3 ms·
> That second example is write-only code, Tacking on the `-d ''` flag to `read` makes it write-only!? > In bash, that's a 50-line monstrosity. In Ruby (or Pyt
by LukeShu 8y ago
> That second example is write-only code,
Tacking on the `-d ''` flag to `read` makes it write-only!?
> In bash, that's a 50-line monstrosity. In Ruby (or Python, or even Perl), it's ~5 clean, readable lines.
As an example, let's take the input, batch it in to groups of 5, filter-out files that don't contain "token1" or "token2", and pass those as a NUL-separated list to stdin of another binary.
I wrote that up. It came out to 10 lines in either Ruby or Bash. https://pastebin.com/EksRxmVJ https://pastebin.com/EksRxmVJ It's certainly not a "50-line monstrosity"
> Again, in another language, that's just a repeated application of a shellescape function as you're composing things. In bash... I have no idea.
In Bash, "shellescape" is pronounced "printf %q" (and shelljoin is pronounced "printf '%q '"). In Bash, that's similarly just repeated application of printf %q:
base_cmd=('whatever' 'arg with space')
nested_cmd=(sh -c "$(printf '%q ' "${base_cmd[@]}")")
ssh user@hostname "$(printf '%q ' "${nested_cmd[@]}")"
For direct comparison with Ruby:
escaped=$(printf '%q ' "${base_cmd[@]}")
escaped=base_cmd.map{|a|shellescape(a)}.join(" ")
escaped=shelljoin(base_cmd)
Or, Bash 4.4 gained @Q as a shorthand for that:
base_cmd=('whatever' 'arg with space')
nested_cmd=(sh -c "${base_cmd[*]@Q}")
ssh user@hostname "${nested_cmd[*]@Q}"
> In most scripting languages, you've got functions like Pathname#relative_path_from(dir) and Pathname#clean_path.
You mean $(realpath --relative-to=DIR) and $(realpath)?
> Or how about a "recursively collapse empty or pseudo-empty directory" function? ... it's ~10 lines of Ruby. Try to implement it in Bash!...
And it's 6 lines of Bash?
find \( -not -type d \
-not -name .DS_Store \
-not -name Thumbs.db \
-not -name Desktop.ini \
-not -name '._*' \
\) -printf '%h\0%p\0' | sort -uz
Or were you looking for a listing of pseudo-empty directories?
comm -z23 <(find -type d -print0 | sort -z) \
<(find \( -not -type d \
-not -name .DS_Store \
-not -name Thumbs.db \
-not -name Desktop.ini \
-not -name '._*' \
\) -printf '%h\0%p\0' | sort -uz)
Like I said, there are things that are hard to do in Bash, but I'm not certain you've pointed any of them out.
> There is a reason that the default kind of cloud-init script is a YAML file, rather than a Bash script.
And there's also a reason why a "GNU/Linux distro" is essentially a giant pile of shell scripts (maybe embedded in to Makefiles).
> a reason why every /bin/init that isn't sysvinit has soundly beaten sysvinit.
I mean, I'm happy with systemd on my systems, but: OpenRC is a giant pile of shell scripts.
> the parts where you do file manipulation are abstracted out, ... through domain-specific file-manipulation binaries (dpkg)
Great example, because the core of Debian is the "rules" file for each package, which is... a Makefile, containing shell.
Or, look at Arch Linux, the core of which is the "PKGBUILD" file for each package, which is... a Bash script.
> or through pre-written and thoroughly-checked file-manipulation Bash scripts (autotools).
Autoconf is targeting shells that aren't Bash. It's pre-written and thoroughly-checked shell that contains wonky workarounds, in order to be compatible with buggy shells from 25 years ago.
If you're assuming "modern shell / utilities", file manipulation in Bash isn't hard.
- jwilk 8y ago> You mean $(realpath --relative-to=DIR) and $(realpath)? For historical reasons, some old Debian-based distros (such as Ubuntu 14.04 LTS "Trusty Tahr") doesn't ship with coreutils' realpath. Instead, they have separate, somewhat incompatible realpath package, which doesn't have --relative-to. Also, beware that $() strips trailing newlines.
- LukeShu 8y agoGood mention! I knew that realpath is a relatively new addition to GNU coreutils, but I didn't realize that older Debians had an incompatible implementation of it. With a single $(realpath ...), chomping the trailing newline is fine, as realpath will insert a trailing newline after the filename; but definitely something to be aware of with other commands.
- jwilk 8y agoNewline in realpath (and many other commands) output is mostly for aesthetics. It doesn't help with taming $(), because $() strips all trailing newlines, including the ones that were actually part of the pathname. You would have have to resort to something as ugly as: f=$(realpath ... && printf 'x') f=${f%x}
- etatoby 8y agoI've been writing Bash scripts (among other things) professionally for 15 years and I didn't know (and had never seen used) most of those tricks: read -d'', printf %q, @Q, all those -z options, and even realpath. Bash and other shell scripting makes simple data manipulation (filter/map/reduce/groupBy...) a royal pain in the butt and something incredibly hard to get right, without introducing horrible bugs under the surface of your script. The underlying reason is that shells were born as human interfaces for the basic UNIX calling convention for processes (ARGV and return code) and their input/output ports (byte-based stdin/stdout) both of which are hopelessly inadequate to represent even the most basic data structures, and thus need convoluted rules in place to be of any use (see the getopt conventions, including the double dash, and all the CSV-like input/output formats, including newline- and zero-terminated arrays of strings.) Take the example of command arguments: as the article points out, mixing options and argument arrays into the same flat array of strings (ARGV) is a recipe for disaster. Compare it with the calling convention in any modern programming language, where an array of filenames is a single well-defined item, and a boolean flag or other option is a completely different item. Couple this shaky foundation with the complex escaping and interpolation rules added by shells to make it useable by people (single and double quotes, backslashes, dollars, globs... nest and repeat, nest and repeat...) and you get a house of cards built on sand. The only redeeming quality of Bash and UNIX in general is that it's very easy to write something that works most of the time. Whether you want to write something flaky like that to "get the job done," or something that will work reliably all the time, hopefully with no security flaws or other bugs lurking under the surface, is up to you. See Worse is Better and the New Jersey approach that spawned C, UNIX and ultimately Bash: https://www.jwz.org/doc/worse-is-better.html https://www.jwz.org/doc/worse-is-better.html Personally, I'll be phasing out my Bash usage more and more, in favor of strongly typed languages, even to do "scripting" tasks. I will also be looking more at PowerShell, which was designed to solve this very problem and which I have ignored for too long.