Get bash to respect quotes when word splitting subshell output
Hire the world's top talent on demand or became one of them at Toptal: https://topt.al/25cXVn
--------------------------------------------------
Music by Eric Matyas
https://www.soundimage.org
Track title: A Thousand Exotic Places Looping v001
--
Chapters
00:00 Get Bash To Respect Quotes When Word Splitting Subshell Output
01:11 Accepted Answer Score 17
03:45 Thank you
--
Full question
https://superuser.com/questions/1529226/...
--
Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...
--
Tags
#bash #bashscripting
#avk47
ACCEPTED ANSWER
Score 17
Bash really doesn't have a good way to parse a string into substrings, while respecting quotes. Whether it's coming from a command expansion (that is, $( )
-- what I think you're calling a subshell) or a plain variable expansion ($varname
).
- If you double-quote the expansion, no splitting at all is done.
- If you don't double-quote the expansion, it splits on whitespace, but doesn't pay attention to quotes or escapes. It also tries to expand anything that looks like a filename wildcard, which can cause comedy and/or tragedy.
- If you use
eval
on a double-quoted expansion, it parses all shell syntax, including other command and variable expansions, redirects, multiple commands with;
or&
, etc. Lots of opportunity for bad results here. - If you use
eval
on a non-quoted expansion, you get the split-on-whitespace-and-expand-wildcards effect, followed by a regular full parsing. Pretty much everything can go wrong here. read -a
is the best of a bad lot. It completely fails at respecting quotes, but at least it doesn't expand filename wildcards.
So bash itself can't do this. But xargs
can -- its default split-into-words parsing respects quotes and escapes, so depending on the situation you might be able to use it directly:
$ echo "\"'foo bar'\" 'one two'" | xargs args
2 args: <'foo bar'> <one two>
There are a couple of potential problems with this: For one thing, depending on how much output there is, xargs
might decide to split them between multiple runs of the command. You can adjust this to some extent with its -n
, -s
, and -x
options, but it's not entirely satisfactory.
Another possible problem is that if the command is actually a shell function, complex command, or builtin you want to execute in the current shell, this won't work. You can adapt it, but it's messy; you need to use xargs printf '%s\0'
to convert to a null-delimited sequence of strings, then use a while IFS= read -r -d ''
loop to convert that to a bash array, and then finally you can do something with the array:
$ argarray=()
$ while IFS= read -r -d '' arg; do argarray+=("$arg"); done < <(echo "\"'foo bar'\" 'one two'" | xargs printf '%s\0')
$ args "${argarray[@]}"
2 args: <'foo bar'> <one two>
Note that this uses a process substitution with <( )
. This is a bash-only feature, and won't even work when bash is in sh-emulation mode. So you must start your script with an explicit bash shebang (#!/bin/bash
or #!/usr/bin/env bash
) (and don't override the shebang by running the script with sh scriptname
).