TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

When Bash scripts bite

233 pointsby yminskyabout 8 years ago

30 comments

mambodogabout 8 years ago
I highly recommend ShellCheck[0] if you&#x27;re writing any bash. With the warnings and stylistic advice it provides, I feel like I can actually be confident that my scripts are doing what I think they&#x27;re doing.<p>[0]: <a href="https:&#x2F;&#x2F;github.com&#x2F;koalaman&#x2F;shellcheck" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;koalaman&#x2F;shellcheck</a>
评论 #14321951 未加载
评论 #14324074 未加载
评论 #14327087 未加载
评论 #14322567 未加载
评论 #14325453 未加载
peffabout 8 years ago
This doesn&#x27;t mention one of my favorite bash gotchas, which is using `set -e` with `pipefail` at all. Try this:<p><pre><code> set -euo pipefail yes | head </code></pre> That will consistently exit because `yes` gets sigpipe and quits. Which is expected, but triggers a script exit. But more exciting is that something like:<p><pre><code> generate_data | head </code></pre> only _sometimes_ fail. It&#x27;s a race that depends on whether generate_data is able to stuff all of its data into the pipe buffer before head calls close().<p>EDIT: I seemed to remember sharing this bug not too long ago, and indeed I did. pixelbeat responded with some interesting links: <a href="https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=13940628" rel="nofollow">https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=13940628</a>
评论 #14324318 未加载
评论 #14323886 未加载
Shivatronabout 8 years ago
As an aside, I like reading Jane Street&#x27;s blog. They&#x27;re one of the few companies in our space (I&#x27;m also in &quot;automated trading&quot;) that discusses even non-proprietary stuff openly. When you work in an information vacuum, it&#x27;s comforting to know that presumably similar people face the same challenges.<p>When the author wrote &quot;a particular production bash script (if that doesn&#x27;t sound horrifying, hopefully it will by the end of this post),&quot; I couldn&#x27;t help but smile...
评论 #14322470 未加载
评论 #14322097 未加载
networkedabout 8 years ago
The error handling is one reason I prefer Tcl to Bash or POSIX sh if I need a nontrivial script that shells out to other programs. Tcl handles errors, substitution, etc. properly (unsurprisingly) by default:<p><pre><code> &gt; set foo [exec &#x2F;bin&#x2F;true][exec &#x2F;bin&#x2F;false][exec &#x2F;bin&#x2F;true] child process exited abnormally while executing &quot;exec &#x2F;bin&#x2F;false&quot; &gt; exec true | false | true child process exited abnormally while executing &quot;exec true | false | true&quot; &gt; set sp &quot;hello world&quot;; exec echo $sp; # No need to quote $sp there. hello world </code></pre> Not all shell-like things are as convenient to do in Tcl as they are in sh (the most significant difference to me is that you cannot pipe to or from functions), and it is more verbose, but because everything is a string in Tcl I find that it integrates with *nix (or Windows!) command line programs better than other scripting languages. E.g.,<p><pre><code> &gt; lmap x [split [exec ps | tail -n +2] \n] {lindex $x 0} 4540 5767 5768 31161 </code></pre> What happens here is that you take the output of `ps | tail -n +2`, split it on newlines, then map over it treating each line (that is something like &quot;5814 pts&#x2F;0 00:00:00 ps&quot;) as a list and taking the first element in it. The result is a list of PIDs (a string containing the PIDs separated by whitespace).<p>I can recommend trying Tcl to anyone fighting Bash who doesn&#x27;t want to replace it with Python&#x2F;Ruby&#x2F;etc. If you try it, though, use version 8.6 or at the very least 8.5. The previous versions are EOL but are still common in the wild. If a recent Tcl is not available on your system, you can build a self-contained static binary interpreter with <a href="http:&#x2F;&#x2F;kitcreator.rkeene.org&#x2F;kitcreator" rel="nofollow">http:&#x2F;&#x2F;kitcreator.rkeene.org&#x2F;kitcreator</a>.
评论 #14337132 未加载
评论 #14325377 未加载
unhammerabout 8 years ago
Not that people should be expected to know this, but here&#x27;s an idiom that does work:<p><pre><code> if res=&quot;$(ldap-query-for-valid-users)&quot;; then echo &quot;($res)&quot; &gt; &quot;&#x2F;tmp&#x2F;all-users.sexp&quot; else handle_failure fi </code></pre> I second the recommendation to use <a href="https:&#x2F;&#x2F;github.com&#x2F;koalaman&#x2F;shellcheck" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;koalaman&#x2F;shellcheck</a> – you really shouldn&#x27;t be writing shell scripts without it – but in this case it doesn&#x27;t seem to handle the issue (with default settings at least).
评论 #14324023 未加载
ben0x539about 8 years ago
Slightly more useful than `set -e` is `set -E` and `trap exit ERR`.<p>But still there&#x27;s basically no way to make this consistently useful.<p>Even if you religiously set -e&#x2F;E in every scope just in case, if you&#x27;re anywhere in a scope inside the non-final operand of a bunch of &amp;&amp;&#x2F;||s, or as the conditional expression in a control structure or whatever, -e&#x2F;E will just do nothing, you can&#x27;t turn it any more on, you just don&#x27;t get early termination on errors no matter how many nested function calls you&#x27;re actually removed from the original ||. It&#x27;s not great.
dmorenoabout 8 years ago
I was just thinking how cool would it be to have a module in python to really ease generating bash style scripts, with less overhead than normal `subprocess` methods have... And there was it just a Google search away: <a href="https:&#x2F;&#x2F;pypi.python.org&#x2F;pypi&#x2F;sh&#x2F;1.12.13" rel="nofollow">https:&#x2F;&#x2F;pypi.python.org&#x2F;pypi&#x2F;sh&#x2F;1.12.13</a>
评论 #14322334 未加载
评论 #14324713 未加载
评论 #14322655 未加载
评论 #14322208 未加载
评论 #14325257 未加载
评论 #14322198 未加载
dtomaabout 8 years ago
Does error handling in your bash scripts ever become annoying enough to warrant a rewrite in, say, OCaml?
评论 #14321568 未加载
评论 #14321410 未加载
评论 #14323537 未加载
评论 #14321375 未加载
评论 #14321330 未加载
zwischenzugabout 8 years ago
I was in a meeting with a major vendor and a bunch of fintech leads recently and major vendor&#x27;s techie said &#x27;...no-one likes bash scripts...&#x27;<p>After the meeting I said to colleagues, &#x27;I quite like bash scripts, actually&#x27;, and they all said &#x27;I thought that too...&#x27;
评论 #14322232 未加载
评论 #14322311 未加载
ben0x539about 8 years ago
Another fun one is that<p><pre><code> set -e export x=$(false) echo ok </code></pre> prints ok, but<p><pre><code> set -e export x x=$(false) echo ok </code></pre> exits early because of the `false`.
评论 #14325735 未加载
评论 #14324028 未加载
jwilkabout 8 years ago
<p><pre><code> echo ... &gt; &quot;&#x2F;tmp&#x2F;all-users.sexp&quot; </code></pre> No, this is not a secure way to create temporary files.<p>Please use mktemp(1).
评论 #14325346 未加载
RijilVabout 8 years ago
What this really boils down to is you should avoid subshells where possible and handle this with either variable assignment or and streams (pipes).<p>eg:<p>set -euo pipefail<p>foo() {<p><pre><code> false echo &quot;hello world&quot; } </code></pre> variable=$(foo) # fails<p>foo | do_stuff # fails<p>My preference is to handle things as streams
shmerlabout 8 years ago
Yes, subshells can be tricky. Don&#x27;t rely on stuff like pipefail, rather check return codes and react accordingly. Bash also provides a way to read return codes of piped commands. See PIPESTATUS in man bash.
10165about 8 years ago
<p><pre><code> echo ($(ldap-query-for-valid-users)) &gt; &#x2F;tmp&#x2F;all-users.sexp </code></pre> should be something like<p><pre><code> x=$(ldap-query-for-valid-users); test ${#x} -gt 0||exec echo no valid users &gt;&amp;2; echo \(&quot;$x&quot;\) &gt; &#x2F;tmp&#x2F;all-users.sexp; </code></pre> This way they would get the message &quot;no valid users&quot; to stderr and the script would exit. According to the blog post that is what they wanted.<p>Alternatively,<p><pre><code> x=$(ldap-query-for-valid-users); test ${#x} -gt 0||exit 100 echo \(&quot;$x&quot;\) &gt; &#x2F;tmp&#x2F;all-users.sexp; </code></pre> if they prefer a nonzero exit code to a message to stderr.
infinity0about 8 years ago
A related annoyance is that when you write a for-loop in a Makefile, every iteration gets run even if one of them fails, due to how the shell calculates exit codes of for-loops.<p>So most of the time you should do &quot;set -e; for XXX&quot;. Otherwise your Makefile loops will &quot;succeed&quot; incorrectly.
iamNumber4about 8 years ago
How about just writing safe code.<p>Really... Check your damn return values. set -e is a crutch of a sloppy programmer.<p>Ok, yes; you can do this really slick thing in one line by stringing together a bunch of commands. However, just because you can does not mean you should.<p>Bash makes it simply with &#x27;if ! &lt;command&gt;; then &lt;failure commands&gt; fi&#x27;. Try not to string ten things together. Keep conditional true state in your scripts execution flow.
评论 #14324535 未加载
评论 #14327246 未加载
评论 #14324384 未加载
wdrozabout 8 years ago
I use Xonsh[0] for scripting now. I explain why in my blog[1], basically because we are in 2017 and I like python.<p>[0]: <a href="http:&#x2F;&#x2F;xon.sh&#x2F;" rel="nofollow">http:&#x2F;&#x2F;xon.sh&#x2F;</a><p>[1]: <a href="https:&#x2F;&#x2F;william-droz.com&#x2F;xonsh-a-modern-shell-that-enable-python-in-your-terminal.html" rel="nofollow">https:&#x2F;&#x2F;william-droz.com&#x2F;xonsh-a-modern-shell-that-enable-py...</a>
评论 #14322133 未加载
jstimpfleabout 8 years ago
I don&#x27;t think the problem is (ba)sh the language, but rather the idioms it implements.<p>It&#x27;s usually a bad idea to begin writing the result before knowing that all input is there. That&#x27;s like a server announcing a 200 OK and beginning to stream, only later detecting I&#x2F;O error. It&#x27;s difficult to deal with such a server as a client.<p>More generally, in pipelines we deal with pairs of programs that are only connected by a text stream, with no possibility to communicate out-of-band conditions. In `PRODUCER | CONSUMER`, PRODUCER can&#x27;t tell from a sigpipe whether CONSUMER crashed or has read all the data it needs. And CONSUMER can&#x27;t know whether PRODUCER crashed or if it should take action on the results.<p>Most scripts don&#x27;t really need the take-action-immediately level of concurrency. It&#x27;s sometimes nice to be concurrent (use multiple CPUs at once). But the cases where the processed data can&#x27;t be buffered at least in a temporary file before taking action are really rare.
supremesaboteurabout 8 years ago
Some more edge cases : <a href="http:&#x2F;&#x2F;mywiki.wooledge.org&#x2F;BashFAQ&#x2F;105" rel="nofollow">http:&#x2F;&#x2F;mywiki.wooledge.org&#x2F;BashFAQ&#x2F;105</a>
therealmarvabout 8 years ago
When I see very long bash scripts I get angry. There is a reason why perl &amp; later python were invented and they are both in 98% of cases available on Unix compatible systems.<p>Not that I&#x27;m not ok with short bash scripts or for systems were you only have bash... but if your Unix compatible system only has bash there is something else wrong with your system in my point of view.
评论 #14322341 未加载
评论 #14322214 未加载
评论 #14322507 未加载
评论 #14322987 未加载
sambeabout 8 years ago
Yeah, naively using errexit won&#x27;t get you the whole way. BashFAQ, ShellCheck and StackOverflow have quite a bit of information, and you can often write things that are pretty robust if you take in all the information. Particular cases that come to mind are:<p>1) command substitution. you can catch this with an explicit error handler inherited by child processes. 2) pipefail SIGPIPE false positives. pretty hairy, command dependent whether this is a &quot;real&quot; error. often not, so you can work around it by ignoring SIGPIPE 3) process substitution. As far as I know, there is no way to workaround this whilst still using the convenience syntax. You have to carefully use explicit named pipes and carefully use wait on the PIDs (carefully!). Maybe still with races...<p>In my experience, you can write moderately robust shell scripts if you care enough and use all these flags and linters. But by the time you are at this stage you probably shouldn&#x27;t be using shell scripting. More like training to spot problems in other people&#x27;s code.
a_bonoboabout 8 years ago
This works but it gets ugly when you have to use &#x27;set -e&#x27; everywhere:<p>set -e<p><pre><code> foo() { &#x2F;bin&#x2F;false echo &quot;foo&quot; } echo &quot;$(set -e; foo)&quot;</code></pre>
mannykannotabout 8 years ago
The use of global values within programs is generally deprecated, for good reasons. The use of global values that modify the semantics of its interpreter probably deserves even more skepticism.<p>The inheritance of these flags by subshells, which may have been written assuming different semantics, would be potentially even more problematic, so I think bash is right in this case, though arguably the inheritance could be limited to subshell code defined in the same file.
memracomabout 8 years ago
set -euo pipefail should NEVER be used in a bash script. If you feel tempted to do so, then you are trying to make the shell scripting language be something that it is not.<p>This is the time for a full scale programming language such as Python or perhaps Groovy on the JVM or Go language. When you need to write robust code, use the tools that were created for writing robust code.<p>Bash just has too many quirks.<p>Note that this is related to the most common way that people build a Big Ball of Mud. You have a simple app and you need a couple of features so you add them on. Rinse and repeat. Before too long you have an app that does too much and was never designed&#x2F;architected to do that much stuff. You are probably ignoring a number of techniques for integrating functionality in large apps such as message queueing, microservices, separate libraries or packages, multiple languages.<p>Shell scripts suffer the same trajectory towards too much complexity. When you see it happening, and before the task gets too complex, replace the script with an app and apply all the normal software engineering techniques to make it robust.
评论 #14332553 未加载
equaluniqueabout 8 years ago
I wasn&#x27;t sure about the title of this post chose to view it when I saw janestreet.com
knownabout 8 years ago
Use sh -c<p>-c Read commands from the command_string operand instead of from the standard input. Special parameter 0 will be set from the command_name operand and the positional parameters ($1, $2, etc.) set from the remaining argument operands.
tobyhinloopenabout 8 years ago
I still think Bash scripts (or unix scripts, or shell scripts, or whatever they are called) are awful.
user5994461about 8 years ago
Use ansible instead of bash.<p>Everything bash can do, ansible can do it better.
draw_downabout 8 years ago
There are so many awful things about shell scripting. It&#x27;s always so difficult to do simple things with it.
nialv7about 8 years ago
Why write new bash scripts in this day and age...
评论 #14321902 未加载
评论 #14321784 未加载
评论 #14322452 未加载
评论 #14322223 未加载
评论 #14321987 未加载
评论 #14324033 未加载