[HN Gopher] Safe Shell String Interpolation
___________________________________________________________________
Safe Shell String Interpolation
Author : Wingy
Score : 9 points
Date : 2025-03-22 17:37 UTC (5 hours ago)
(HTM) web link (samwing.dev)
(TXT) w3m dump (samwing.dev)
| amelius wrote:
| This is one reason why, really, nobody should use a shell that
| was optimized for commandline use, for scripting.
|
| On the commandline, all your inputs are usually known and
| trusted, so the shell language will take a few shortcuts to make
| everything more convenient for the user. But of course, when
| scripting, these assumptions don't hold and you need a language
| with more notational rigor.
|
| Bash et al. are dangerous scripting languages, and they become
| even more dangerous when users (who typically don't want to
| understand the subtleties) start using LLMs to generate code for
| them.
| BoingBoomTschak wrote:
| That example is pretty sad, the solution has always been the
| same (even in sh), use an argv based wrapper instead of
| something like system(3): exec(`figlet
| "Welcome, ${username}" | lolcat -f`) // Should be
| spawn(["sh", "-c", "figlet \"Welcome, $1\" | lolcat -f",
| "argv0", username])
|
| > This is one reason why, really, nobody should use a shell
| that was optimized for commandline use, for scripting.
|
| Tcl showed me you can have your cake and eat it, you just need
| sane quoting rules (like don't make IFS splitting the default
| and use matched braces for your quoting syntax to solve
| nesting) and arrays to escape the infamous quoting hell.
|
| Though I agree that sh is dangerous for newbies, I always
| advise "1. ALWAYS set -eu, 2. shellcheck" to them.
| amelius wrote:
| > Though I agree that sh is dangerous for newbies
|
| The problem is that expert users of sh will write scripts
| that newbies may at some point modify. This would be much
| safer if the scripts were originally in a better language.
|
| Bash is a footgun. Even expert users have shot themselves in
| the foot with it at some point in their career.
| telotortium wrote:
| No love for Python's shlex.quote[1] or their equivalents in other
| languages to quote arguments for shell? In his case, which is
| interpolating a variable inside a shell double-quoted string, I
| would probably use environment variables, or else use shell
| `printf` with `shlex.quote`. But generally I interpolate directly
| into the command line using `shlex.quote`, when I can't avoid
| executing using the shell.
|
| I was happy to learn about Python's template string proposal
| (i.e., t-strings) from https://peps.python.org/pep-0750/,
| although it probably won't become common for a while.
|
| [1] https://docs.python.org/3/library/shlex.html#shlex.quote
| Wingy wrote:
| Yes, you can quote the values and embed them directly, but what
| if you want to do something like this?
| shell('echo "Hello, {user}"')
|
| You would have to do something like:
| arg_for_echo = f"Hello, {user}" shell(f"echo
| {shlex.quote(arg_for_echo)}")
|
| The main drawback of quoting values like that is that you can't
| embed them inside an argument in the shell. The environment
| variable "trick" I used avoids this problem.
|
| Using quotes to sanitize arguments like that is how google/zx
| (library for writing shell/JS hybrid scripts) handles
| arguments: https://google.github.io/zx/quotes
| js2 wrote:
| It's not that hard to setup a pipeline in Python w/o invoking the
| shell: username = input("Hello, what's your
| name? ") p1 = Popen(["figlet", f"Welcome, {username}"],
| stdout=PIPE) p2 = Popen(["lolcat", "-f"],
| stdin=p1.stdout, stdout=PIPE) p1.stdout.close() # Allow
| p1 to receive a SIGPIPE if p2 exits. banner =
| p2.communicate()[0] print(banner)
|
| https://docs.python.org/3/library/subprocess.html#replacing-...
|
| Don't use the shell unless you absolutely have to, and when you
| do have to, use shlex.quote for quoting arguments:
|
| https://docs.python.org/3/library/shlex.html#shlex.quote
| username = input("Hello, what's your name? ") banner =
| check_output(f"figlet "Welcome, {quote(username)}" | lolcat -f",
| shell=True) print(banner)
|
| For something this simple, you could also just use
| `subprocess.check_output` twice: username =
| input("Hello, what's your name? ") banner =
| check_output(["figlet", f"Welcome, {username}"]) banner =
| check_output(["lolcat", "-f"], input=banner)
| print(banner)
___________________________________________________________________
(page generated 2025-03-22 23:01 UTC)