{"id":5303,"date":"2026-01-23T10:37:34","date_gmt":"2026-01-23T16:37:34","guid":{"rendered":"https:\/\/neosmart.net\/blog\/?p=5303"},"modified":"2026-01-23T14:55:23","modified_gmt":"2026-01-23T20:55:23","slug":"portable-cartesian-brace-expansion-in-your-shell","status":"publish","type":"post","link":"https:\/\/neosmart.net\/blog\/portable-cartesian-brace-expansion-in-your-shell\/","title":{"rendered":"Portable (Cartesian) brace expansion in your shell"},"content":{"rendered":"<p>Cartesian expansion, also known as brace expansion, is an incredibly powerful feature of most unixy shells, but despite being fundamentally simple and incredibly empowering, it&#8217;s been traditionally relegated to the dark and shadowy corners of command line hacking, employed only by greybeards looking to avoid repeating themselves at any cost. And, boy, does it really cut down on repetition.<\/p>\n<p>Take for example this snippet from a <code>Dockerfile<\/code> that sets up permissions on certain directories:<\/p>\n<pre><code class=\"language-bash\">mkdir -p \/var\/log\/php \/var\/log\/unitd \/var\/log\/mysql\r\nchown -R user:user \/var\/log\/php \/var\/log\/unitd \/var\/log\/mysql\r\nchmod ug+rw \/var\/log\/php \/var\/log\/unitd \/var\/log\/mysql\r\n<\/code><\/pre>\n<p><!--more--><\/p>\n<p>You can immediately see how the <code>\/var\/log\/<\/code> prefix is a source of repetition: wasting valuable horizontal screen real estate, extra room for typos or mistakes, and diluting the signal:noise ratio of the lines in question.<\/p>\n<p>In most shells,<sup id=\"rf1-5303\"><a href=\"#fn1-5303\" title=\"Including, but not limited to, fish (the one true shell), bash, zsh, ksh (as of ksh88), zsh, and csh\/tcsh (where this feature originated). But notably not supported by dash and not part of the posix sh spec.\" rel=\"footnote\">1<\/a><\/sup> you can use braces (<code>{}<\/code>)<sup id=\"rf2-5303\"><a href=\"#fn2-5303\" title=\"These are called braces, or sometimes curly braces. They are not brackets ([]) and there is no such thing as a &ldquo;curly bracket&rdquo;.\" rel=\"footnote\">2<\/a><\/sup> to accomplish the same with much less repetition:<\/p>\n<pre><code class=\"language-bash\">mkdir -p \/var\/log\/{php,unitd,mysql}\r\nchown -R user:user \/var\/log\/{php,unitd,mysql}\r\nchmod ug+rw \/var\/log\/{php,unitd,mysql}\r\n<\/code><\/pre>\n<p>..which expands to the same, effectively operating as a declarative &#8220;for loop&#8221; running for each of the items within the braces; e.g. <code>echo {hello,goodbye}_world<\/code> prints <code>hello_world goodbye_world<\/code> (<em>m<\/em>x<em>n<\/em> complexity with each braced\/non-braced component being one set) while <code>echo {hello,goodbye}_world{.com,.exe}<\/code> prints <code>hello_world.com goodbye_world.com hello_world.exe goodbye_world.exe<\/code>, or\u00a0<em>m<\/em>x<em>n<\/em>x<em>o<\/em> complexity with\u00a0<em>m<\/em> equal to two,\u00a0<em>n<\/em> equal to one, and\u00a0<em>o<\/em> equal to two for four results.<\/p>\n<p>Depending on the shell, this functionality can be taken a step further and used to do questionable things, like combining with glob expansion to get a list of all files with a certain extension that appear in one of\u00a0<em>n<\/em> different directories with an expression like <code>foo\/{bar,baz}\/*.{c,h}<\/code> or anything else your heart desires.<\/p>\n<p>Alas, the one issue with Cartesian\/brace expansion is that it isn&#8217;t formalized in the <a href=\"https:\/\/pubs.opengroup.org\/onlinepubs\/9699919799.2018edition\/utilities\/V3_chap02.html\" rel=\"follow\">posix <code>sh<\/code> spec<\/a>, meaning you can&#8217;t use it in truly portable shell scripts or with a <code>#!\/bin\/sh<\/code> shebang. But fret not, because you can still accomplish the same thing with another feature: subshell output interpolation.<\/p>\n<p>To jump right to it, here is portable syntax that will accomplish the same as our original demonstration snippet, except this is truly portable and you can use it in your Alpine Linux <code>Dockerfile<\/code> running <code>sh<\/code>:<\/p>\n<pre><code class=\"language-bash\">mkdir -p $(printf \"\/var\/log\/%s\\n\" php unitd mysql)\r\nchown -R user:user \/var\/log\/$(echo php unitd mysql)\r\nchmod ug+rw \/var\/log\/$(printf \"%s\\n\" php unitd mysql)\r\n<\/code><\/pre>\n<p>As you can see, there are several ways it can be used, relying on <code>printf<\/code> to perform the expansion for you directly (taking advantage of the fact that <code>printf<\/code> is spec&#8217;d to repeat the input when it is provided with more parameters than placeholders), use <code>echo<\/code> instead of <code>printf<\/code> if your <code>$IFS<\/code> configuration splits on spaces, or use <code>printf %s\\n<\/code> if you want to support spaces in paths and have <code>$IFS<\/code> set to split only on new lines (or you&#8217;re smart and sane and use <a href=\"https:\/\/github.com\/fish-shell\/fish-shell\/\" rel=\"nofollow\"><code>fish<\/code><\/a> which defaults to splitting only on new lines anyway) \u2013 in which case the second example with <code>echo<\/code> instead of <code>printf %s\\n<\/code> won&#8217;t work for you.<\/p>\n<p>So keep it DRY<sup id=\"rf3-5303\"><a href=\"#fn3-5303\" title=\"Don&rsquo;t Repeat Yourself\" rel=\"footnote\">3<\/a><\/sup> and embrace the power of brace expansion (or Cartesian expansion in general, if you don&#8217;t have access to brace expansion in your shell of choice) the next time you&#8217;re banging out a shell script!<\/p>\n<hr class=\"footnotes\"><ol class=\"footnotes\"><li id=\"fn1-5303\"><p>Including, but not limited to, <a href=\"https:\/\/github.com\/fish-shell\/fish-shell\/\" rel=\"nofollow\">fish<\/a> (the one true shell), bash, zsh, ksh (as of ksh88), zsh, and csh\/tcsh (where this feature originated). But notably not supported by dash and not part of the posix <code>sh<\/code> spec.&nbsp;<a href=\"#rf1-5303\" class=\"backlink\" title=\"Jump back to footnote 1 in the text.\">&#8617;<\/a><\/p><\/li><li id=\"fn2-5303\"><p>These are called braces, or sometimes curly braces. They are not brackets (<code>[]<\/code>) and there is no such thing as a &#8220;curly bracket&#8221;.&nbsp;<a href=\"#rf2-5303\" class=\"backlink\" title=\"Jump back to footnote 2 in the text.\">&#8617;<\/a><\/p><\/li><li id=\"fn3-5303\"><p>Don&#8217;t Repeat Yourself&nbsp;<a href=\"#rf3-5303\" class=\"backlink\" title=\"Jump back to footnote 3 in the text.\">&#8617;<\/a><\/p><\/li><\/ol>","protected":false},"excerpt":{"rendered":"<p>Cartesian expansion, also known as brace expansion, is an incredibly powerful feature of most unixy shells, but despite being fundamentally simple and incredibly empowering, it&#8217;s been traditionally relegated to the dark and shadowy corners of command line hacking, employed only &hellip; <a href=\"https:\/\/neosmart.net\/blog\/portable-cartesian-brace-expansion-in-your-shell\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":505,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[999,1],"tags":[1039,1031,1005,1040,813],"class_list":["post-5303","post","type-post","status-publish","format-standard","hentry","category-programming","category-software","tag-expansion","tag-fish","tag-shell","tag-tips-and-tricks","tag-unix"],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p4xDa-1nx","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts\/5303","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/users\/505"}],"replies":[{"embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/comments?post=5303"}],"version-history":[{"count":5,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts\/5303\/revisions"}],"predecessor-version":[{"id":5309,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts\/5303\/revisions\/5309"}],"wp:attachment":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/media?parent=5303"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/categories?post=5303"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/tags?post=5303"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}