7

I wanted:

#!/bin/bash
cmd --options \
    option=value,\
    option=value,\
    option=value,\
    option=value

But running with bash -x I got:

cmd --options option=value, option=value, option=value, option=value

That causes an error.

How can I do this so bash doesn't automatically place this blank space?

3
  • 4
    Hello and welcome to unix.se! fwiw: command is not the best name to use as an exemple, as it is a bash builtin: command foo : bypasses any alias/function foo and instead launches foo from $PATH. You should rename it to something else ( my_script ? ) to avoid confusions Commented Nov 16, 2023 at 17:04
  • 3
    @olivier-dulac Hello, thank you very mutch for the tip!
    – rhuanpk
    Commented Nov 16, 2023 at 18:20
  • I am sorry, but I cannot tell what you are trying to do. If "cmd" is a bash script, you can give it options as arguments, they will be called $1, $2, $3 by default and can be renamed. If "cmd " is an external program, it probably has its own way of assigning options.
    – Wastrel
    Commented Nov 17, 2023 at 14:54

4 Answers 4

30

Well, you do have whitespace at the beginning of each line. This is not auto-removed by bash, although it is squeezed into one single space.

In order to completely avoid it, write the script as

#!/bin/bash
command --options \
opt1=val1,\
opt2=val2,\
opt3=val3
7
  • Nice, your explanation was good and I understood. Is there a way to do this while keeping the tabs?
    – rhuanpk
    Commented Nov 16, 2023 at 13:53
  • 10
    The spacing is autoremoved. Those blank characters are part of the shell syntax to delimit arguments that are to be passed to the command, they are not included in those arguments. xtrace shows spaces between those argument because xtrace shows shell code that could be used to run the same command, and one space separator is the shortest and simplest way to do it. Commented Nov 16, 2023 at 16:55
  • 1
    @PeterCordes, there's no word splitting here, just syntax tokenisation, only the Bourne shell did word (IFS) splitting on (some) literal words. Other Bourne like shells only do it on the result of expansions ($param, $((arith)), $(cmd)) as required by POSIX (while POSIX prohibits the splitting behaviour of the Bourne shell). Commented Nov 18, 2023 at 10:57
  • 1
    @PeterCordes, To put Stéphane's point in an example, in heirloom-sh, IFS=l; echo hello prints he o and you need to quote "hello" to protect it from splitting. The distinction is also important wrt. other syntax elements, like quotes. People tend to try to put to quotes inside variables and then are surprised it doesn't do what they want. (e.g. s='"foo bar" doo'; printf "%s\n" $s prints three lines and literal double-quotes.) Of course it doesn't work because word splitting is not the same as syntax processing.
    – ilkkachu
    Commented Nov 18, 2023 at 13:24
  • 1
    @PeterCordes, that page says the same thing as I did above. There is no word splitting here, because there's none of the expansions that word splitting applies to. Word splitting is a separate step that is done long after the shell code is tokenised (on whitespace, ;, |...) and parsed (into simple commands, complex structures, etc...). Commented Nov 18, 2023 at 17:56
26

If you need to pass a string like opt1=val1,opt2=val2,... as a single argument to a command, but want to have it in multiple lines in the script for editability, you could use an array as an intermediate step and then join all the array elements when running the command.

E.g.

opts=(
    opt1=val1    # no commas here 
    opt2=val2    # but comments also work
    etc.
)
(IFS=,
 echo somecmd --options "${opts[*]}"   # echo for demonstration
)

That prints somecmd --options opt1=val1,opt2=val2,etc.

(The first pair of parenthesis is part of the array assignment syntax, the second starts a subshell to shield the rest of the script from the modified IFS. That subshell might not be necessary, esp. if the script doesn't do anything afterwards, or if you just reset IFS later or don't use it at all.)

Obviously that requires a feature-rich shell with support for arrays. In addition to Bash, zsh would be fine. In ksh, you'd need to quote the strings (or at least the equal sign), so it doesn't turn it into a struct-like compound variable.


If you require a POSIX sh compatible solution, the nearest equivalent to an array would be the list of positional parameters, but there's only one set of them, and since set acts more or less like a regular command, you still need to remember the backslashes (and can't use comments).

Instead, something like this should work:

args=$( <<EOF sed -e 's/^[[:space:]]*//' | tr '\n' ,
    opt1=val1
    opt2=val2
    etc.
EOF
)
args=${args%,}
echo somecmd --options "$args"  # echo for demonstration
7
  • This makes it gratuitously bash-specific rather than proper shell script. Commented Nov 17, 2023 at 17:28
  • 1
    @R..GitHubSTOPHELPINGICE, it's not Bash-specific. Arrays are supported in e.g. ksh and zsh in addition to Bash. In any case, Bash was explicitly mentioned in the question.
    – ilkkachu
    Commented Nov 17, 2023 at 19:36
  • 4
    @R, bash was actually quite late to the party (1996) when it comes to arrays (compared to csh (1979), ksh (1983), rc (1989), zsh (1991) at least). Its array design is mostly shaped after that of ksh88 though the a=(x y) is from zsh (where it was inspired by csh) while the equivalent in ksh88 is with set -A. By the time bash added arrays, ksh93 (the new rewrite of ksh) was already out which did have var=(...) to define compound variables including but not limited to plain arrays. Commented Nov 17, 2023 at 20:56
  • Would you prefer I word it differently? I don't really care if it's only bash or bash and also zsh and ksh. The point is that if you write this you cannot put #!/bin/sh in your script and expect it to work. You need to make it specifically require an interpreter (e.g. bash, but yes, could be another one) that supports this functionality. Commented Nov 20, 2023 at 21:16
  • @R..GitHubSTOPHELPINGICE, no-one was putting #!/bin/sh there (and that /bin/sh is also an interpreter)
    – ilkkachu
    Commented Nov 20, 2023 at 21:17
10

Another option:

#!/bin/bash
cmd --options "$(printf "%s" \
    "option=value," \
    "option=value," \
    "option=value," \
    "option=value"
)"

printf "%s" prints the arguments without any whitespace, so it results in:

cmd --options option=value,option=value,option=value,option=value

Some commands are also able to remove whitespace entirely on their own, provided it's passed as a single argument. So simple quoting (to prevent word splitting) can be enough, for example ffmpeg seems to be fine with this:

ffmpeg -i somefile.mp4 -vf "
    hflip,
    vflip,
    hue=s=0
" result.mp4

But that depends on how the command you're using handles these options.

Yet another option may be using --options itself multiple times (if your command allows that), in case of ffmpeg that doesn't work, but sometimes it's fine.

9

If you want to be able to put each options in its own line, you may want to try another approach:

options="opt1=val1,"
options+="opt2=val2,"
options+="opt3=val3"
your_command --options "${options}"
# note: "command" being a bash builtin, I replaced it by "your_command"
# (command foo : bypasses any alias/function "foo" and instead launches "foo" from $PATH)
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .