[HN Gopher] Bash $* and $@ (2017)
       ___________________________________________________________________
        
       Bash $* and $@ (2017)
        
       Author : oftenwrong
       Score  : 179 points
       Date   : 2020-01-12 18:00 UTC (4 hours ago)
        
 (HTM) web link (eklitzke.org)
 (TXT) w3m dump (eklitzke.org)
        
       | pletnes wrote:
       | I highly recommend this site. It's very nitpicky, and that's the
       | only way to write somewhat robust shell scripts.
       | https://mywiki.wooledge.org/BashGuide
        
       | dwheeler wrote:
       | It's not just bash, this is true for all POSIX shells (including
       | dash, bash, ksh, and so on).
       | 
       | If you're doing a lot of complex calculations, shells are the
       | wrong tool for the job. But if it's a relatively small program
       | whose primary task is invoking other programs on a Unix-like
       | system, shells are still a decent choice. The biggest problems
       | with shells are handled by using shellcheck, so if you're writing
       | shell scripts, use shellcheck.
        
       | ljm wrote:
       | Man, one of the worst debugging experiences of my life was when
       | we built an extensigble build tool, based on Bash. Most of it
       | worked, but there was always an inconsistency between $* and $@
       | and there would be PRs that swapped those values around, back and
       | forth.
       | 
       | They were both totally valid; we just hadn't agreed on a calling
       | convention for the tool so people were trying to fix their
       | individual problems based on their own habits.
        
         | a1369209993 wrote:
         | > there was always an inconsistency between $* and $@
         | 
         | There is never a legitimate reason to use $* . Even in the rare
         | cases where you _want_ those semantics (hint: you don 't), you
         | should use something like "$(join ' ' "$@")" instead.
         | 
         | > They were both totally valid;
         | 
         | No, they weren't.
        
         | mikelward wrote:
         | They are not both totally valid.
         | 
         | You almost always want "$@".
         | 
         | If you're using $* , you're not supporting arguments that
         | contain spaces, and you'll break if arguments contain
         | wildcards. If you're using "$*" , you're treating multiple
         | arguments as a single argument.
         | 
         | https://unix.stackexchange.com/a/41595/3169
        
       | baby wrote:
       | That's why I hate bash and Makefiles. The syntax is just so
       | cryptic that if you don't write/read bashfiles/Makefile for a
       | while it's just impossible to get back into it.
        
         | eikenberry wrote:
         | I agree to an extent about Makefiles, though I still think they
         | are useful as long as you keep to a simple subset of their
         | functionality.
         | 
         | Shell scripting is a different matter. One of the great things
         | about shell scripting is that you can never forget it because
         | you are using it constantly to interact with your system. The
         | fact that it is something you use constantly and can just take
         | and stick in a file and re-use is one reason why shell scripts
         | are so popular.
        
       | 3xblah wrote:
       | https://www.in-ulm.de/~mascheck/various/bourne_args
        
       | jblow wrote:
       | Why are we still using this in 2020?
        
         | bori5 wrote:
         | bash is ubiquitous, and for me who grew up pre-python, which I
         | probably should start learning for bash like tasks.
        
           | jblow wrote:
           | Toxic waste sites were ubiquitous too, but we put in the
           | effort to clean them up.
        
             | grayed-down wrote:
             | I'm game. What's your plan?
        
         | yjftsjthsd-h wrote:
         | Because nobody has written a good (universal) alternative.
         | POSIX sh is available everywhere and is extremely well suited
         | to gluing things together.
        
           | HorstG wrote:
           | If you need a portable replacement, there is Perl. Available
           | since the last century. If you don't care about obscure
           | Unixes, there is also Python and a whole bunch of other
           | scripting languages on more modern systems.
        
           | baby wrote:
           | It never matters in practice, just bootstrap your environment
           | by installing python and use a python script.
        
             | peterwwillis wrote:
             | What do you bootstrap the python environment with?
        
           | jblow wrote:
           | Obviously, but then the answer is, why? What the hell is our
           | problem?
           | 
           | Even if you think shell scripting is a good idea (which I
           | don't), just fix all the obviously dumb toxic stuff like this
           | and put out a shell that is minimally different with only
           | semantic fixes. Emit warnings now for toxic semantics but
           | still support them, and in a couple of years, turn off that
           | support for good.
        
             | gpvos wrote:
             | _> in a couple of years, turn off that support for good._
             | 
             | Make that at least 50 years, if not 100. You have no
             | control over every place where shell scripts are run.
        
             | peterwwillis wrote:
             | I think the problem is the people who don't understand the
             | difference between "obviously dumb toxic stuff" and
             | "features".
             | 
             | This thread is all about the difference between $* and $@.
             | The difference is explained in the man pages for most
             | shells, but since most programmers don't read instructions,
             | they often need blog posts to explain to them how a
             | documented feature of a language works.
             | 
             | I highly recommend the _dash_ man page
             | (http://man7.org/linux/man-pages/man1/dash.1.html) as a
             | concise explanation of portable shell syntax. From the
             | _dash_ man page, under _Special Parameters_ :
             | *            Expands to the positional parameters, starting
             | from one.                       When the expansion occurs
             | within a double-quoted string it
             | expands to a single field with the value of each parameter
             | separated by the first character of the IFS variable, or
             | by a <space>  if IFS is unset.               @
             | Expands to the positional parameters, starting from one.
             | When the expansion occurs within double-quotes, each posi-
             | tional parameter expands as a separate argument.  If there
             | are no positional parameters, the expansion of @ generates
             | zero arguments, even when @ is double-quoted.  What this
             | basically means, for example, is if $1 is "abc" and $2 is
             | "def ghi", then "$@" expands to the two arguments:
             | "abc" "def ghi"
             | 
             | It turns out we didn't need a blog post to explain it,
             | because it's in the manual that nobody reads. But we should
             | definitely complain about how this crafty, unusual piece of
             | obviously dumb toxic stuff works, because how were you
             | supposed to know to RTFM?
             | 
             | To answer your question "why still in 2020?", it's because
             | these are independent features people needed. Sometimes
             | people wanted the $* semantics, and sometimes the $@
             | semantics. So both exist. It's up to you to learn how the
             | system works and use it properly.
             | 
             | It's not like Python doesn't also have weird edge cases
             | that you won't know until you learn the whole language.
             | I've seen people spend hours futzing about with lambdas and
             | list comprehensions to try to fix a bug, which I addressed
             | by just rewriting the expressions as regular-old loops and
             | data structures. Bash isn't uniquely bad, it has warts like
             | everything else. Take out the warts you don't want and
             | someone else will complain that they're missing.
        
             | shuspect wrote:
             | Just do it.
        
               | sullyj3 wrote:
               | I feel like "busy working on a different programming
               | language" is one of the better excuses for not doing
               | this.
        
       | lallysingh wrote:
       | http://tldp.org/LDP/abs/html/
        
         | teddyh wrote:
         | _Advanced Bash-Scripting Guide_ , working link:
         | 
         | https://www.tldp.org/LDP/abs/html/
         | 
         | (Your link, without "www", gives me a certificate error.)
        
           | lallysingh wrote:
           | It was http. How'd you get a cert error?
        
       | mattbillenstein wrote:
       | Oh man, wish I'd seen this a few days ago - ended up doing
       | something ugly using printf and xargs...
        
         | gpvos wrote:
         | Use perl instead.
        
           | mattbillenstein wrote:
           | WORSE
        
         | mikelward wrote:
         | You should try stackoverflow or unix.stackexchange.com.
         | 
         | e.g. my answer https://unix.stackexchange.com/a/41595/3169
        
           | mattbillenstein wrote:
           | Hmm, still not able to do what I want with this -- I want to
           | take an argument like:
           | 
           | foo.sh --clean bar.yml
           | 
           | And actually run something like:
           | 
           | blah -e '{"clean": true}' bar.yml
           | 
           | where -e and the thing in '' are two separate args...
        
       | acdha wrote:
       | If you're writing shell scripts you should have
       | https://www.shellcheck.net/ in your editor and pre-commit hooks
       | to catch common footguns.
       | 
       | Even then, my threshold for "this should be Python" has shrunk
       | over the years: I used to say "greater than one screen of code"
       | but now it's more like ">1 branch point or any non-trivial scalar
       | variable".
        
         | npmaile wrote:
         | second this. We recently added it to a project at my company
         | (https://github.com/homedepot/spingo) as part of a github
         | action and it is awesome. A quick search for the specific code
         | in the shellcheck wiki reveals the problem and a solution. I've
         | had no real issues with it yet.
        
         | skocznymroczny wrote:
         | To be honest, for most of my scripting needs I usually start
         | with Python directly and just go mass os.system() or
         | commands.get_output() calls. Later on I refactor into
         | subprocess.Popen as needed
        
         | 3xblah wrote:
         | shellcheck /scriptsdir/script
         | 
         | I noticed this in the output.                   ^-- SC2148:
         | Tips depend on target shell and yours is unknown. Add a
         | shebang.
         | 
         | Being new to shellcheck, not familar with options or what it
         | does, so I hastily and erroneously typed:
         | shellcheck -shell=bash script
         | 
         | Note I learned UNIX via NetBSD. I prefer and use their version
         | of ash _for both interactive and scripting use_.1 I never got
         | used to  "--" GNU-style long options. I sometimes type a single
         | "-" out of habit. Anyway, here is the output I got from
         | shellcheck:                   Unknown shell: hell=bash
         | 
         | I agree with shellcheck.
         | 
         | Although there may be some irony in the fact it cannot sort out
         | it own argument parsing.
         | 
         | 1. I do not use other scripting languages such as Python, Perl,
         | Ruby, etc. That means, e.g., for quick and dirty one-offs and
         | prototyping, I can omit the shebang. Debian's "dash" scripting
         | shell is derived from NetBSD's ash, the one I choose for
         | interactive use.
        
           | therein wrote:
           | Sounds like it would be content with `-sbash`.
        
           | inetknght wrote:
           | The error message is quite clear:                   SC2148:
           | Tips depend on target shell and yours is unknown. Add a
           | shebang.
           | 
           | If you google what a shebang is, the top link for me is a
           | Wikipedia article on the subject [0]. A shebang is basically
           | just a line (always the first line) in a file which tells the
           | operating system what program to invoke to execute the
           | script. There are different shells beyond just bash, so
           | shellcheck wants to know which flavor the shell is written
           | for and uses the shebang to figure it out.
           | 
           | I always have the top of my shell scripts with a shebang,
           | even if the script isn't intended to be directly executed.
           | 
           | Pick the user's bash from PATH environment:
           | #!/usr/bin/env bash
           | 
           | Or specify a specific bash:                   #!/bin/bash
           | 
           | Or use whatever plain-shell is installed:
           | #!/bin/sh
           | 
           | Or maybe it's a Python script:
           | #!/usr/bin/env python3
           | 
           | Or it's a text file:                   #!/usr/bin/env vi
           | 
           | If you're not using shebangs then you're probably writing
           | your scripts wrongly.
           | 
           | [0] https://en.wikipedia.org/wiki/Shebang_(Unix)
        
             | [deleted]
        
             | [deleted]
        
         | kylek wrote:
         | My rule of thumb is- if I think I'm going to need to use an
         | array in bash, I'm probably doing something wrong and better
         | just use python.
        
           | pinopinopino wrote:
           | I don't know, I often miss the pipe operator in python, which
           | makes certain things much easier. I once created a small
           | class to simulate this a bit, it allows you to use + as pipe
           | operator. Wouldn't use it in production though, was just fun
           | to write it. https://pastebin.com/eQacwLj7
        
             | swiley wrote:
             | Doesn't python have channels? The pipe is essentially a
             | channel combined with a close message (a null object I
             | guess) Otherwise you can do something similar with
             | iterators and function composition. I guess the syntax
             | isn't quite as easy to read if you're not used to function
             | composition?(which seems surprisingly difficult for some
             | beginners)
        
               | fiddlerwoaroof wrote:
               | Shell pipes are fairly low overhead because they are an
               | os primitive.
        
             | andrewshadura wrote:
             | Tried python-sh?
        
             | ehsankia wrote:
             | Why not use | as the pipe operator? Just use __or__ instead
             | of __add__
             | 
             | I have done something similar too once in a shell-like
             | Python CLI I worked on. Can also use __ror__ to be able to
             | pass primitives into your commands like `[1, 2, 3] |
             | SumCmd() | PrintCmd()`
             | 
             | I do agree that typing something like that, especially when
             | working in a shell, is much nicer than having to go back
             | and forth all the time to type `print(sum([1,2,3]))`
             | 
             | Here it is mentioned in a talk about Python Aesthetics by
             | Brandon Rhodes:
             | https://www.youtube.com/watch?v=x-kB2o8sd5c&t=8m24s
        
         | sverhagen wrote:
         | I suppose I had a more forgiving day, the other day, and told
         | someone the threshold was hundred lines. Anyway, _some value_.
         | 
         | It just challenged my colleague to make the code denser, to
         | stay within the given limit. _Sigh._
        
           | BurningFrog wrote:
           | As someone who likes small fonts and big screens, for me a
           | screen is often a lot more than 100 lines.
        
             | kjeetgill wrote:
             | I don't think this is like the 80 character line length
             | that's about screen size. This 100 line limit is framed as
             | a quick and dirty for heuristic for script complexity.
        
           | michaelmrose wrote:
           | What about some sort of complexity checking more precise than
           | lines of code? What about trying to write one thing you would
           | normally write in shell with something else per unit of time.
        
         | mysterydip wrote:
         | A common problem that happened to a coworker was he made a
         | quick bash script for something simple, then kept adding "just
         | one more" thing with sunk cost fallacy not wanting to take the
         | time to rewrite it. Eventually the monstrosity created was too
         | difficult to debug and it had to be rewritten in a different
         | language.
        
         | koala_man wrote:
         | I keep posting this, but my favorite rule of thumb came from a
         | Google dev infra engineer who said that "every Python script
         | over 100 lines should be rewritten in bash, because at least
         | then you won't fool yourself into thinking it's production
         | quality"
        
           | echelon wrote:
           | That's a cute heuristic, but I think the better practice is
           | to distrust scripts without tests as they can quickly diverge
           | from the rest of the codebase.
           | 
           | It's still better to script in Python or Ruby than Bash.
           | Nobody understands Bash. It's even more mysterious than Perl.
        
             | fiddlerwoaroof wrote:
             | I'd rather write bash for orchestration than nearly
             | anything else: bash is designed to make coordinating
             | processes easy, something very few programming languages
             | have managed to do.
        
               | laumars wrote:
               | The thing that gets me about all the new shells and shell
               | scripting languages popping up these days is they loosely
               | seem to fall into 2 categories:
               | 
               | 1. more emphasis traditional programming paradigms (be it
               | JS, LISP, Python, whatever) which leaves a platform that
               | is arguably a better designed language but however is a
               | poorer REPL environment for it. Bash works because it's
               | terse and terseness is actually preferable for "write
               | many, read once" style environments like an interactive
               | command prompt.
               | 
               | 2. or they spend so much effort supporting POSIX/Bash --
               | including their warts -- that they end up poorer
               | scripting languages.
               | 
               | I think what we really need isn't to rewrite all our
               | shell scripts in Python but rather better shells. Ones
               | which work with existing muscle memory but isn't afraid
               | to break compatibility for the sake of eliminating a few
               | footguns. Shells that can straddle both the
               | aforementioned objectives without sacrificing the other.
               | But there doesn't seem to be many people trying this (I
               | can only think of a couple off hand).
        
               | jhasse wrote:
               | What are your thoughts about fish?
        
               | fiddlerwoaroof wrote:
               | Does fish still have the issue where pipelines aren't
               | really concurrent?
        
               | fiddlerwoaroof wrote:
               | If you haven't tried scripting in zsh, I'd give it a go.
               | zsh has a lot of safety improvements and quality of life
               | features.
               | 
               | https://news.ycombinator.com/item?id=22029662
        
               | laumars wrote:
               | It's also subject to many of the same footguns as Bash so
               | I'd put that into the 2nd camp (re my previous post).
               | 
               | Not that I'm taking anything away from zsh. It is a nice
               | shell. But I think we can do even better considering how
               | dependant we still are on shells for day to day stuff.
        
               | fiddlerwoaroof wrote:
               | I realize that it has similar footguns, however reading
               | through their info pages, I was surprised by how many
               | they just decided to fix, unless you explicitly turn on
               | compatibility mode.
        
               | fiddlerwoaroof wrote:
               | > Zsh had arrays from the start, and its author opted for
               | a saner language design at the expense of backward
               | compatibility. In zsh (under the default expansion rules)
               | $var does not perfom word splitting; if you want to store
               | a list of words in a variable, you are meant to use an
               | array; and if you really want word splitting, you can
               | write $=var.
               | 
               | https://unix.stackexchange.com/a/26672
        
           | yodsanklai wrote:
           | The Google shell style guide [1] says this:
           | 
           | "If you are writing a script that is more than 100 lines
           | long, you should probably be writing it in Python instead.
           | Bear in mind that scripts grow. Rewrite your script in
           | another language early to avoid a time-consuming rewrite at a
           | later date."
           | 
           | [1] https://google.github.io/styleguide/shell.xml
        
             | a3n wrote:
             | Related, the longer your shell script gets, the greater
             | potential for accumulated foot-guns. When you finally do
             | the rewrite, will you be bug-compatible? And once
             | discovered, will you be confident on which side the bug
             | lies?
        
             | HocusLocus wrote:
             | "If you are writing a script that is more than 100 lines
             | long, you should probably be writing it in _Perl_ instead.
             | "
             | 
             | This is true, especially of Python scripts.
        
         | acemarke wrote:
         | I'm a big fan of using Plumbum [0] for writing more complex
         | shell-script-like logic as Python.
         | 
         | [0] https://plumbum.readthedocs.io/en/latest/
        
           | moopling wrote:
           | Wow this looks great, thanks!
        
         | gdevenyi wrote:
         | I still find python incredibly inconvenient for pipelining a
         | ton of unix style command line tools together. Bash still wins
         | there.
        
         | scbrg wrote:
         | Another rule of thumb would be: "How many years from now do you
         | still want this to work?" If I run a shell script I wrote ten
         | years ago, it works. If I run a Python script I wrote ten years
         | ago, it's quite likely to fail with a SyntaxError.
         | 
         | This, I say as someone who _loves_ Python and I use it as my
         | primary language both privately and at work. But I have to
         | admit, Python scripts do not really age well.
        
           | np_tedious wrote:
           | I'm really struggling to think of an example that doesn't
           | involve py2 --> py3. Can you share a few?
        
       | thennegah wrote:
       | Lol, we just made a bash function using this.
       | 
       | // echo_and_run() { echo "$*" ; "$@" ; }
       | 
       | Logs the cmd before running.
        
         | gcmeplz wrote:
         | You can also use `set -xv` to get nice debugging logs
         | 
         | https://www.gnu.org/software/bash/manual/html_node/The-Set-B...
        
       | throw7 wrote:
       | At one time, I did think python would replace my bash shell.
       | 
       | Then I tried it. Either I'm an old dog or I was naive.
       | 
       | Both are true, I think.
        
       | HocusLocus wrote:
       | This is priceless. Countless times I have learned -- then later
       | forgotten -- to use "$@" ... especially in cygwin's Windows
       | 'spaces in filenames' territory
        
         | orev wrote:
         | At this point in time (i.e. 21st century), any *nix script or
         | program that doesn't handle spaces in files names is woefully
         | buggy. Maybe 20 years ago this was excusable, but not now.
         | Spaces can reasonably be expected to be in filenames in all
         | systems.
         | 
         | Support for other "special" characters in filenames (e.g.
         | newlines), however, could still be debatable.
        
           | StillBored wrote:
           | I might say that allowing spaces (newlines, quotes, asterisk
           | , and various other "control" characters) in the filenames is
           | the real bug. Sure its cool that you can put anything you
           | want in a filename, but do you really need them? Particularly
           | on a command line oriented OS? If the entire OS's experience
           | was windows explorer style interactions or C binary strings
           | like manipulation then fine.
           | 
           | This causes nothing but problems, all to avoid a simple
           | character filter..
           | 
           | https://www.tecmint.com/manage-linux-filenames-with-
           | special-...
           | 
           | See how many "errors" you can find in that article. There are
           | a bunch, consider the touch *, example if your using rm...
        
             | joshuaissac wrote:
             | A space is a completely normal character that users quite
             | reasonably expect to be able to have in filenames. Treating
             | it as a regular character is not a bug. There are lots of
             | Linux programs that can deal with spaces in filenames
             | already.
        
           | Sharlin wrote:
           | It just seems that the shell semantics (and to an extend
           | those of other Unix tools) are specifically designed to strip
           | off a level of quoting/escaping and perform word splitting
           | the moment you lose your focus for a bit if you're even aware
           | of all the arcane rules in the first place.
        
           | enriquto wrote:
           | > any *nix script or program that doesn't handle spaces in
           | files names is woefully buggy.
           | 
           | On the contrary! It is filesystems that support plain spaces
           | in filenames that are broken. Filenames are variable names.
           | Allowing separators in them is bonkers. I make a point of
           | carefully crafting my scripts to wreak havoc whenever a user
           | has spaces in their filenames.
        
             | michaelmrose wrote:
             | File names are an interface used by normal people to name
             | files. Teaching everyone to name their files differently
             | seems unlikely to succeed.
             | 
             | There is no particular reason your shell can't distinguish
             | between a space inside a filename and the space between
             | tokens in output. The fact that you have to do anything at
             | all yourself is a bug.
        
               | enriquto wrote:
               | > File names are an interface used by normal people to
               | name files. Teaching everyone to name their files
               | differently seems unlikely to succeed.
               | 
               | Sure. But there's no reason why typing the spacebar on a
               | GUI to input your filename should produce a file with a
               | plain space on the filesystem. It could be a unicode non-
               | breaking space, for example.
        
               | a1369209993 wrote:
               | Or, you know, a underscore, as has been de-facto standard
               | for deacades?
        
               | anoncake wrote:
               | That would just make working with files programmatically
               | harder. The issue isn't that file names contain spaces
               | but that we use programming languages that use spaces to
               | separate fields/entries instead of proper data
               | structures.
        
               | enriquto wrote:
               | Do these languages allow spaces in their variable names?
               | 
               | Filenames _are_ the variable names of shell scripting.
        
               | oalae5niMiel7qu wrote:
               | Wrong. Shell scripts have actual variables. They start
               | with $. Filenames are one possible _value_ you might
               | store in these variables.
        
             | thanatropism wrote:
             | Wat
        
             | anoncake wrote:
             | GREATI~1.DEA
        
       | hapless wrote:
       | It's 2020. Friends don't let friends write shell scripts.
        
         | BossingAround wrote:
         | When I first came into the world of SWE, I thought "why would
         | anyone use Bash nowadays when we have Python?"
         | 
         | A colleague answered me: "If we left, there's around 500 people
         | in this building who could support my Bash script, and around
         | 50 Python people."
         | 
         | It kind of stuck with me. At this point, I feel like Bash is
         | one of the common tongues between all tech roles (that deal
         | with Linux, that is).
         | 
         | Python, while nice, is a lot more niche, since you really have
         | to be into development to know Python, while every sysadmin
         | worth their salt can debug a Bash script. And, of course, every
         | SWE, TSE, DOE, .... worth their salt know Bash scripts as well.
         | 
         | If you want to get a job (in the Linux land), bash scripting is
         | typically an "of course".
        
           | dnpp123 wrote:
           | This highly depends on the building you are in, I guess.
        
           | baby wrote:
           | woot, this is definitely wrong in my experience. Most people
           | can read/write python to some degree, and even if you don't
           | know python you can quickly ramp up to understand a config
           | file or a simple program.
           | 
           | On the other hand most people don't write bash scripts and
           | usually have to deal with them when encountering legacy
           | systems or languages.
        
         | etaioinshrdlu wrote:
         | me: What if there was a programming language as inconsistent
         | and terrible as English, maybe it would be uniquely easy for
         | humans to understand?
         | 
         | bfox (wrote bash): Nah, Bash disproves that.
        
           | zbentley wrote:
           | larry wall (wrote perl): hold my beer
        
           | krackers wrote:
           | Applescript takes the cake there. Instead of standard
           | indexing/dereferencing rules in the object hierarchy you
           | instead have this nested mess of "tell X .... end tell"
        
         | arpa wrote:
         | I swear this is my last shell script.
        
         | humblebee wrote:
         | What do you recommend instead?
        
       | HorstG wrote:
       | There are lots of those footguns in shellscript. One should
       | always try to avoid any shell and rather use python, tcl, perl or
       | powershell. Any criticism one might have about insecure and
       | broken by design languages apply doubly to shell.
       | 
       | A short list of possible problems (of course depending on the
       | shell in question):
       | 
       | spaces in filenames
       | 
       | newlines in filenames
       | 
       | nonprintables in filenames
       | 
       | empty variables and their expansion ([ x$foo = "xsomething" ])
       | 
       | errors in pipes
       | 
       | environment madness
       | 
       | /bin/bash ?= /bin/sh
       | 
       | Arrays or the lack of it
       | 
       | Space separates lists as arrays
       | 
       | #!bash vs. #!/bin/bash vs. #!/usr/bin/env bash vs.
       | #!/usr/sfw/bin/bash vs. ...
       | 
       | Unwritable and unreadable control structures (if [], case,
       | &&,...)
       | 
       | Information leaks via ps
       | 
       | and many others...
       | 
       | Never use shell except to search for and invoke a sensible
       | language. And anything is more sensible, including C, Perl,
       | brainfuck and Basic.
        
         | TheDong wrote:
         | I believe your diatribe is misplaced.
         | 
         | There are quite a few pitfalls in shell scripting. You can
         | considerably reduce them by limiting yourself to only being
         | compatible with modern versions of bash and settings things
         | like pipefail, nounset, etc etc.
         | 
         | I do agree that in general a good programming language will be
         | a better option.
         | 
         | > anything is more sensible, including C, Perl, brainfuck and
         | Basic
         | 
         | I do disagree with that however. A 5 line bash script may be
         | 500 lines of C, will take a hundred times longer to write, and
         | may contain memory safety issues (which the bash script at
         | least wouldn't).
         | 
         | I know brainfuck is hyperbolic so I won't argue against that.
         | Something with no filesystem or process forking abilities
         | obviously can't be used for any real task.
         | 
         | I think perl and basic have just as bad syntax as bash though,
         | if not worse. Basic's penchant for "GOTO" is awful, perl's
         | syntax as a whole is just as peculiar as bash's in many places.
         | 
         | I guess my overall point is that bash is usually not a good
         | option compared to modern languages, but it's a darn sight
         | better than you give it credit for. I think it still has its
         | place for 5 or 10 liners that are easy to express and read in
         | bash and don't need any abstractions beyond what coreutils
         | provide.
        
           | HorstG wrote:
           | I agree that 5 to 10 lines might be a sensible upper limit
           | where a shell can safely be used.
           | 
           | Basic does have Goto, but modern dialects do have all the
           | usual control structures. Perl has weird syntax, but far less
           | dangerous footguns: e.g. there are proper arrays, as opposed
           | to many shells. One can distinguish between an empty and an
           | undefined string. One can declare variables and there is the
           | notion of data types. There are even things like taint mode.
           | In shell, you can't even properly iterate over a directory
           | without nasty surprises.
           | 
           | Same in C. Yes, there are memory safety problems, but those
           | are outnumbered by far by shellscripts exploitable via some
           | expansion or variable injection. Its just that thankfully
           | nobody uses shellskripts as network services, so you don't
           | see as many reports about that.
           | 
           | And yes, brainfuck was there as hyperbole. But I truly
           | believe that there are very few things worse than shell for
           | programming.
        
         | diegocg wrote:
         | Many of these crazy corner cases are the reason why I switched
         | to fish. Eg, in fish all variables are arrays, so arguments are
         | passed in the $argv array, and number of elements in the array
         | is "count $argv" (in bash the number of arguments is passed in
         | $#, but the hieroglyphic required to count the number of
         | elements in an array is ${#var[@]} )
         | 
         | $PATH is just an array where each path is an element, so
         | removing or adding a path equals to adding/removing an element
         | from the array which is trivial.
         | 
         | Iterating over files names with spaces works exactly as
         | expected and no IFS tweaking is needed (fun thing, in bash,
         | doing for i in * .foobar; do echo $i; done; in a directory with
         | no files with that extension will return the string "
         | __*.foobar "). No "[" craziness, nicer syntax, etc.
         | 
         | Unfortunately fish still lacks other important features (eg. no
         | set -e equivalent).
         | 
         | There is an space for sane Unix shells but everybody has
         | settled on bash and changing the status quo is difficult.
        
         | AmericanChopper wrote:
         | I like powershell, but it has its own arsenal of footguns for
         | you to use. Things like non-terminating errors and the
         | sometimes confounding behaviour of automatic variables comes to
         | mind.
        
       | ulrikrasmussen wrote:
       | Ick. I have written my fair share of bash, and stuff like this is
       | very common. Most things in bash are just inherently non-
       | compositional and/or is riddled with weird corner cases that you
       | just have to know about in order to not shoot yourself in the
       | foot. This document [0] made the rounds on HN a while back, and
       | it has, together with the associated tool, been something that I
       | have regularly consulted whenever I have had to do anything non-
       | trivial with bash (anything that has to deal with arguments to
       | commands is already non-trivial to get right).
       | 
       | [0]
       | https://github.com/anordal/shellharden/blob/master/how_to_do...
        
         | [deleted]
        
       | marcacohen wrote:
       | Here's an easy way to see how this works. Run this script:
       | 
       | echo dollar-star: for i in $ _; do echo $i; done echo dollar-at:
       | for i in $@; do echo $i; done echo quoted-dollar-star: for i in
       | "$_"; do echo $i; done echo quoted-dollar-at: for i in "$@"; do
       | echo $i; done
       | 
       | ./x.sh a "b c" d dollar-star: a b c d dollar-at: a b c d quoted-
       | dollar-star: a b c d quoted-dollar-at: a b c d
        
         | _kst_ wrote:
         | https://news.ycombinator.com/formatdoc
         | 
         | Blank lines separate paragraphs. Text surrounded by asterisks
         | is italicized, if the character after the first asterisk isn't
         | whitespace.
         | 
         | Text after a blank line that is indented by two or more spaces
         | is reproduced verbatim. (This is intended for code.)
         | 
         | Urls become links, except in the text field of a submission.
        
         | yesenadam wrote:
         | HN gobbled your stars.
        
       | TheDong wrote:
       | The article doesn't mention it, but very similar syntax is also
       | used for arrays.
       | 
       | For example:                   arr=(a b c)         arr+=(d)
       | ls "${arr[@]}" # ls "a" "b" "c" "d"         ls "${arr[*]}" # ls
       | "a b c d"
       | 
       | This has quite nice symmetry with the fact that the 1st argument
       | is "$1", and you replace the number with these symbols, and for
       | arrays you access elements with "${arr[1]}", and again replace
       | the number with the same symbols for the same behaviour.
       | 
       | If you do a lot of bash scripting, arrays are invaluable.
        
         | useragent86 wrote:
         | Indeed, and actual Bash scripting (as opposed to plain POSIX
         | sh) is much more pleasant. Used properly, arrays make it easy
         | to build commands with arguments and finally run them, e.g.
         | #!/bin/bash              command_args=(             --foo bar
         | --baz "buzz buzz"         )              [[ $frob_option ]] &&
         | command_args+=(--frob frab)              echo command_name
         | "${command_args[@]}" "other" "arg"              # Echoes:
         | # command_name --foo bar --baz "buzz buzz" --frob frab other
         | arg
        
       | [deleted]
        
       | j1elo wrote:
       | Thing is, if the script is basically the glue between
       | incantations of multiple other commands (which is basically the
       | intended use case of shell scripting), then replacing that with
       | Python[1] is just adding lots of boilerplate code for no real
       | improvements in functionality. I still agree with a strict limit
       | on the acceptable complexity, though.
       | 
       | Most if not all my shell scripts are just piping executions of
       | external commands. I find all the code needed to properly run a
       | process and process its output is much easier with the UNIX
       | toolbox and a couple of pipe commands, than having to handle all
       | those input/output buffers, command execution modes, etc in any
       | other shell script language.
       | 
       | OTOH Plumbum [2] has been mentioned here, and it seems fantastic
       | for that use case. But I think the issue is obvious, in that it
       | took a conversation in HN to raise awareness of this tool: it is
       | not officially promoted, or recommended even, as the solution for
       | replacing shell scripting, so it is kind of obscure (unless you
       | are actively into the language or somehow by chance end up
       | getting to know about it, that is)
       | 
       | There is also the thing about choosing Python to replace Bash
       | scripts would force having to install Python in all of the
       | project's Docker images, while a short POSIX script works as-is.
       | 
       | [1]: Saying Python because that's the most common suggestion for
       | replacing Bash.
       | 
       | [2]: https://plumbum.readthedocs.io/en/latest/
        
       | lalaland1125 wrote:
       | I highly recommend that people learn how to use
       | https://docs.python.org/3/library/subprocess.html as a
       | replacement for Bash. It's a little more work upfront, but it's
       | vastly more maintainable.
        
         | ben509 wrote:
         | I disagree. Here's a warning from subprocess[1]:
         | 
         | > Use communicate() rather than .stdin.write, .stdout.read or
         | .stderr.read to avoid deadlocks due to any of the other OS pipe
         | buffers filling up and blocking the child process.
         | 
         | The trouble with `communicate()` is it only handles very simple
         | cases, basically, your output has to fit in memory. Same
         | problem exists in asyncio.[2]
         | 
         | Yes, you can usually work around this. That doesn't mean it's a
         | good replacement; whereas complex pipelines are so trivial in
         | bash that any user-defined function can be used in a pipeline,
         | subprocess generally forces you to do the dumb thing and create
         | a mess of temporary files.
         | 
         | And that's not even considering you're writing 10 times as much
         | code than you would to accomplish the same task.
         | 
         | [1]:
         | https://docs.python.org/3/library/subprocess.html#subprocess...
         | 
         | [2]: https://docs.python.org/3/library/asyncio-
         | subprocess.html#as...
        
       | roryrjb wrote:
       | Shell scripting is absolutely still relevant. The rule of thumb
       | should not be about length but about complexity, specifically if
       | you absolutely need something like a real array or a hash then
       | move onto a different language. Use shellcheck and avoid bash for
       | scripting. I use bash or pdksh interactively but stick to POSIX
       | shell for scripting. I am finding myself writing POSIX shell all
       | the time and having great success with it.
        
         | pletnes wrote:
         | Why not bash? It's in most places, even if POSIX is even more
         | general. And it does add some nice scripting features.
         | 
         | I use zsh for interactive and bash for scripts for the same
         | reasons as you, though.
        
           | roryrjb wrote:
           | Yeah don't get me wrong the bashisms are useful, but I'm
           | hopping between OpenBSD, FreeBSD and Linux (and perhaps
           | sharing scripts with my macOS-using colleagues) and although
           | bash is available on all those platforms and more, POSIX
           | shell will work out of the box without any further
           | configuration.
        
           | fiddlerwoaroof wrote:
           | I write my scripts in zsh, because its extensions are most
           | useful in scripts, and it's not too hard just to make it
           | available everywhere you need to work (e.g. you can compile
           | it to be installed in ~/zsh and just copy the installation to
           | your home directory on any machine you need to use.
           | 
           | Things like array-linked variables ($path is an array version
           | of $PATH and modifications to one propagate to the other),
           | associative arrays (dictionaries in python) and a handful of
           | other really nice tools (e.g. saner white space handling)
           | make going back to bash for scripts unpleasant.
        
           | smichel17 wrote:
           | Because when you find yourself needing bash-specific
           | features, alarm bells should be going off. POSIX sh is good
           | at reminding you to KISS and delegate to other tools.
        
         | gameswithgo wrote:
         | it is, but should it be?
        
       | clort wrote:
       | This is not Bash specific, this is basic POSIX shell:
       | 
       | https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...
       | 
       | Bash also incorporates the POSIX shell, but has extensions. This
       | is fine if you are using it, but if you are writing a script
       | which may need to run on another system, its better to keep it to
       | POSIX.
       | 
       | (edit: URL - thanks userbinator)
        
         | [deleted]
        
         | userbinator wrote:
         | You probably meant to link to the shell section of POSIX?
         | 
         | https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...
        
       | gpvos wrote:
       | In the far past, it used to be necessary to use ${1+"$@"} because
       | of some shells that didn't handle "$@" properly when it was
       | empty.
        
         | _kst_ wrote:
         | I've run into that. There was a problem with the OSF/1 /bin/sh
         | that caused "$@" to expand to a single empty argument if there
         | are no arguments, rather than to an empty list as it should.
         | 
         | I just now removed a workaround for that problem from one of my
         | scripts, 17 years after I added it.
         | 
         | https://en.wikipedia.org/wiki/OSF/1
        
       | floatingatoll wrote:
       | The only time you should use $* is inside a debug message like
       | "unable to open $* ($!)". If you're passing around arguments,
       | always use "$@".
       | 
       | If you know enough bash to disagree, you know enough bash to use
       | the third case safely :)
        
         | eikenberry wrote:
         | I was going to say the same thing (so I will). The rule is just
         | always use "$@" and then you have only one case to reason about
         | and it is what you want 99.99% of the time anyways.
        
         | dnautics wrote:
         | I think it's also reasonable to use in ssh and I believe su,
         | which are commands that expect a single string as their inner
         | command parameter.
        
           | floatingatoll wrote:
           | I can count on ten fingers the number of times in twenty
           | years I've worked with another bash coder who did the su and
           | ssh cases correctly without triggering escaping bugs. It's
           | not any insult on them, but it's almost always done
           | incorrectly and happens to work due to the absence of
           | whitespace and backslashes, leading to eventual bugs (that
           | Shellcheck won't always catch). Given:                   #
           | ARGV=( "one two", "three four" )
           | 
           | It's probably safe to recommend "$@" for use with su _only
           | when_ you use -c correctly, as you're locally specifying the
           | args without any further IFS interference. But $* isn't
           | usable:                   # CORRECT         su root -c 'rm
           | "$@"' -- "$@"         rm "one two" "three four"
           | # incorrect         su root -c "rm \"$@\""         rm one two
           | three four    # wrong arguments              # incorrect
           | su root -c "rm" "$@"         rm                       # -c
           | doesn't use arguments              # incorrect         su
           | root -c 'rm "$@"' "$@"         rm "three four"          #
           | loses the first argument (?!)              # incorrect:
           | su root "rm" "$*"         rm one two three four    # wrong
           | arguments              # incorrect:         su root "rm $*"
           | "rm one two three four"  # command not found
           | 
           | It's probably safe to recommend "$@" for use with ssh _only
           | when_ using printf %q to ensure that you escape your
           | arguments for their transit through ssh to the remote host,
           | as otherwise the arguments get corrupted by the extra layer
           | of shell processing. $* isn 't usable here either:
           | # CORRECT         ssh remote -- 'rm '"$(printf '%q ' "$@")"
           | rm "one two" "three four"              # incorrect
           | ssh remote 'rm '$(printf '%q ' "$*")         rm "one two
           | three four"  # wrong arguments              # incorrect
           | ssh remote rm "$@"         rm one two three four    # wrong
           | arguments              # incorrect         ssh remote "rm
           | \"$@\""         rm "one two three four"  # wrong arguments
           | # incorrect         ssh remote 'rm "$@"' "$@"         rm one
           | two three four    # wrong arguments              # incorrect:
           | ssh remote "rm" "$*"         rm one two three four    # wrong
           | arguments              # incorrect:         ssh remote "rm"
           | "$*"         rm one two three four    # wrong arguments
           | 
           | EDIT: Shellcheck misses 3 of the 4 broken su cases, but
           | catches all of the broken ssh cases. (And produced a warning
           | I disagreed with in one of the complete examples, but in the
           | spirit of things, added double quotes to silence it.)
        
       | chubot wrote:
       | Oil [1] supports all of this old syntax to run existing shell
       | scripts, but has new syntax which is more convenient.
       | 
       | - You can write @ARGV instead of "$@".
       | 
       | - You can write @myarray instead of "${myarray[@]}"
       | 
       | (Related: _Thirteen Incorrect Ways and Two Awkward Ways to Use
       | Arrays_ https://www.oilshell.org/blog/2016/11/06.html )
       | 
       | Example:                   oil$ var myarray = @('has spaces' foo)
       | oil$ var s = $'has\ttabs'              # function to print an
       | array element on each line         oil$ lines() { for x in @ARGV;
       | do echo $x; done }              # pass 3 args -- 2 from myarray
       | and 1 from s         oil$ lines @myarray $s         has spaces
       | foo         has     tabs
       | 
       | [1] https://www.oilshell.org/
        
         | dzidol wrote:
         | https://xkcd.com/927/
        
           | chubot wrote:
           | Unlike every other alternative shell, Oil runs existing bash
           | scripts to avoid this problem.
        
       ___________________________________________________________________
       (page generated 2020-01-12 23:00 UTC)