2

I want to define a variable in a script that will be used as extra arguments for a command.

For this question I will use ls, but my real-world usecase is an rsync command with --rsync-path="docker run ...".

set -x
# Set extra arguments.
export LS_ARGS_OVERRIDE='-a --color="hello world"'
# Call `ls` with extra arguments
ls "${LS_ARGS_OVERRIDE}" -l .

Expected result is

+ ls -a --color="hello world" -l .
ls: unsupported --color value 'hello world' (must be always, auto, or never)

I tried the naive way:

echo
echo "Direct usage quoted"
ls "${LS_ARGS_OVERRIDE}" -l .

echo
echo "Direct usage unquoted"
ls ${LS_ARGS_OVERRIDE} -l .

Which resulted in

Direct usage quoted
+ ls '-a --color="hello world"' -l .
ls: invalid option --
usage: ls [-@ABCFGHILOPRSTUWabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...]

Direct usage unquoted
+ ls -a '--color="hello' 'world"' -l .
ls: unsupported --color value '"hello' (must be always, auto, or never)

I then tried using arrays as suggested on this stackoverflow

echo
echo "Input quoted"
MY_LOCAL_VAR=("${LS_ARGS_OVERRIDE:-}")
ls ${MY_LOCAL_VAR[@]} -l .

echo
echo "Everything quoted"
MY_LOCAL_VAR=("${LS_ARGS_OVERRIDE:-}")
ls "${MY_LOCAL_VAR[@]}" -l .

echo
echo "Arguments quoted"
MY_LOCAL_VAR=(${LS_ARGS_OVERRIDE:-})
ls "${MY_LOCAL_VAR[@]}" -l .

echo
echo "Nothing quoted"
MY_LOCAL_VAR=(${LS_ARGS_OVERRIDE:-})
ls "${MY_LOCAL_VAR[@]}" -l .

Which outputs

Input quoted
+ ls -a '--color="hello' 'world"' -l .
ls: unsupported --color value '"hello' (must be always, auto, or never)

Everything quoted
+ ls '-a --color="hello world"' -l .
ls: invalid option --
usage: ls \[-@ABCFGHILOPRSTUWabcdefghiklmnopqrstuvwxy1%,\] \[--color=when\] \[-D format\] \[file ...\]

Arguments quoted
+ ls -a '--color="hello' 'world"' -l .
ls: unsupported --color value '"hello' (must be always, auto, or never)

Nothing quoted
+ ls -a '--color="hello' 'world"' -l .
ls: unsupported --color value '"hello' (must be always, auto, or never)

I finally tried to pass flags after the argument as suggested in this stackoverflow

echo
echo "arguments after"
ls . "${MY_LOCAL_VAR[@]}" -l

To get

Use arguments after
+ ls . '-a --color="hello world"' -l
ls: -a --color="hello world": No such file or directory
ls: -l: No such file or directory
.:
README.markdown

But none of this worked.

Edit 2023 June 20th

The reason I did not use array directly is that LS_ARGS_OVERRIDE is defined in another file.

Let's say There is 2 scripts, A and B, where A define arguments and then calls B:

#  Script A
LS_ARGS_OVERRIDE=(-a --color="hello world")
./B

Then B calls the command with args

# script B
ls "${LS_ARGS_OVERRIDE[@]}" -l .

In this configuration, LS_ARGS_OVERRIDE appears alway empty in script B, while using a string makes it available in B.

themouette
  • 171
  • 1
  • 7
  • 3
    `LS_ARGS_OVERRIDE=(-a --color="hello world"); ls "${LS_ARGS_OVERRIDE[@]}" -l .` – jhnc Jun 19 '23 at 23:22
  • 4
    See [How can I store a command in a variable in a shell script?](https://stackoverflow.com/q/5615717/4154375) and [BashFAQ/050 (I'm trying to put a command in a variable, but the complex cases always fail!)](https://mywiki.wooledge.org/BashFAQ/050). – pjh Jun 19 '23 at 23:28
  • 1
    You need to use an array *instead of* a plain string variable, not *in addition to* the plain string variable. Once you mush multiple complex arguments into a single string, it's very difficult to un-mush them. Converting to an array doesn't help, because it doesn't undo the damage that was done by mushing them together in the first place. – Gordon Davisson Jun 20 '23 at 01:40

1 Answers1

2

I then tried using arrays

So use arrays.

LS_ARGS_OVERRIDE=(-a --color="hello world")
ls "${LS_ARGS_OVERRIDE[@]}" -l .

The reason I did not use array in the definition is the variable LS_ARGS_OVERRIDE is defined in another file

Overall, you should consult the person that stored that in a variable that what they are doing is questionable. The simplest way is to use xargs quote parsing capabilities and happily we do not care about order of arguments here:

xargs ls -l . <<<"$LS_ARGS_OVERRIDE"

If we care some shell wrapping:

xargs sh -c 'ls "$@" -l .' -- <<<"$LS_ARGS_OVERRIDE"

You can use eval, but then it is a security issue.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thank a lot for your answer. The reason I did not use array in the definition is the variable `LS_ARGS_OVERRIDE` is defined in another file. I added this to the original question. – themouette Jun 20 '23 at 12:20
  • (mind, that xargs usage depends on a non-POSIX xargs that allows options _after_ the first argument; granted, GNU systems are common so that's frequently allowed, but it's still worth a caveat) – Charles Duffy Jun 20 '23 at 12:44