{"id":178,"date":"2012-08-22T11:50:32","date_gmt":"2012-08-22T11:50:32","guid":{"rendered":"http:\/\/www.vidarholen.net\/contents\/blog\/?p=178"},"modified":"2012-08-22T11:50:32","modified_gmt":"2012-08-22T11:50:32","slug":"why-bash-is-like-that-subshells","status":"publish","type":"post","link":"https:\/\/www.vidarholen.net\/contents\/blog\/?p=178","title":{"rendered":"Why Bash is like that: Subshells"},"content":{"rendered":"<p>Bash can seem pretty random and weird at times, but most of what people see as quirks have very logical (if not very good) explanations behind them. This series of posts looks at some of them.<\/p>\n<pre>\r\n# I run this script, but afterwards my PATH and current dir hasn't changed!\r\n\r\n#!\/bin\/bash\r\nexport PATH=$PATH:\/opt\/local\/bin\r\ncd \/opt\/games\/\r\n<\/pre>\n<p>or more interestingly<\/p>\n<pre>\r\n# Why does this always say 0? \r\nn=0\r\ncat file | while read line; do (( n++ )); done\r\necho $n\r\n<\/pre>\n<p>In the first case, you can add a <code>echo \"Path is now $PATH\"<\/code>, and see the expected path. In the latter case, you can put a <code>echo $n<\/code> in the loop, and it will count up as you&#8217;d expect, but at the end you&#8217;ll still be left with 0.<\/p>\n<p>To make things even more interesting, here are the effects of running these two examples (or equivalents) in different shells:<\/p>\n<table>\n<tr>\n<td><\/td>\n<td>set in script<\/td>\n<td>set in pipeline<\/td>\n<\/tr>\n<tr>\n<td>Bash<\/td>\n<td style='background-color: #FFC0C0'>No effect<\/td>\n<td style='background-color: #FFC0C0'>No effect<\/td>\n<\/tr>\n<tr>\n<td>Ksh\/Zsh<\/td>\n<td style='background-color: #FFC0C0'>No effect<\/td>\n<td style='background-color: #C0FFC0'>Works<\/td>\n<\/tr>\n<tr>\n<td>cmd.exe<\/td>\n<td style='background-color: #C0FFC0'>Works<\/td>\n<td style='background-color: #FFC0C0'>No effect<\/td>\n<\/tr>\n<\/table>\n<p>What we&#8217;re experiencing are subshells, and different shells have different policies on what runs in subshells.<\/p>\n<p>Environment variables, as well as the current directory, is only inherited parent-to-child. Changes to a child&#8217;s environment are not reflect in the parent. Any time a shell forks, changes done in the forked process are confined to that process and its children. <\/p>\n<p>In Unix, all normal shells will fork to execute other shell scripts, so setting PATH or cd&#8217;ing in a script will never have an effect after the command is done (instead, use <code>\"source file\"<\/code> aka <code>\". file\"<\/code> to read and execute the commands without forking). <\/p>\n<p>However, shells can differ in when subshells are invoked. In Bash, all elements in a pipeline will run in a subshell. In Ksh and Zsh, all <i>except the last<\/i> will run in a subshell. POSIX leaves it undefined.<\/p>\n<p>This means that <code>echo \"2 + 3\" | bc | read sum<\/code> will work in Ksh and Zsh, but fail to set the variable <code>sum<\/code> in Bash. <\/p>\n<p>To work around this in Bash, you can usually use redirection and process substition instead:<\/p>\n<p><code>read sum < <(echo \"2 + 3\" | bc) <\/code><\/p>\n<p>So, where do we find subshells? Here are a list of commands that in some way fails to set foo=bar for subsequent commands (note that all the examples set it in some subshell, and can use it until the subshell ends):<\/p>\n<pre>\r\n# Executing other programs or scripts\r\n.\/setmyfoo\r\nfoo=bar .\/something\r\n\r\n# Anywhere in a pipeline in Bash\r\ntrue | foo=bar | true\r\n\r\n# In any command that executes new shells\r\nawk '{ system(\"foo=bar\") }'h\r\nfind . -exec bash -c 'foo=bar' \\;\r\n\r\n# In backgrounded commands and coprocs:\r\nfoo=bar &\r\ncoproc foo=bar\r\n\r\n# In command expansion\r\ntrue \"$(foo=bar)\"\r\n\r\n# In process substitution\r\ntrue < <(foo=bar)\r\n\r\n# In commands explicitly subshelled with ()\r\n( foo=bar )\r\n<\/pre>\n<p>and probably some more that I'm forgetting.<\/p>\n<p>Trying to set a variable, option or working dir in any of these contexts will result in the changes not being visible for following commands. <\/p>\n<p>Knowing this, we can use it to our advantage:<\/p>\n<pre>\r\n# cd to each dir and run make\r\nfor dir in *\/; do ( cd \"$dir\" && make ); done\r\n\r\n# Compare to the more fragile\r\nfor dir in *\/; do cd \"$dir\"; make; cd ..; done\r\n\r\n# mess with important variables\r\nfields=(a b c); ( IFS=':'; echo ${fields[*]})\r\n\r\n# Compare to the cumbersome\r\nfields=(a b c); oldIFS=$IFS; IFS=':'; echo ${fields[*]}; IFS=$oldIFS; \r\n\r\n# Limit scope of options\r\n( set -e; foo; bar; baz; ) \r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Bash can seem pretty random and weird at times, but most of what people see as quirks have very logical (if not very good) explanations behind them. This series of posts looks at some of them. # I run this script, but afterwards my PATH and current dir hasn&#8217;t changed! #!\/bin\/bash export PATH=$PATH:\/opt\/local\/bin cd \/opt\/games\/ &hellip; <a href=\"https:\/\/www.vidarholen.net\/contents\/blog\/?p=178\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Why Bash is like that: Subshells&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[6,4],"tags":[11,53,39],"class_list":["post-178","post","type-post","status-publish","format-standard","hentry","category-basic-linux","category-linux","tag-bash","tag-linux","tag-why-bash-is-like-that"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/posts\/178","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=178"}],"version-history":[{"count":15,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/posts\/178\/revisions"}],"predecessor-version":[{"id":193,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/posts\/178\/revisions\/193"}],"wp:attachment":[{"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=178"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=178"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=178"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}