1

I need to insert an array of arguments into an eval string executed via bash -c.

In this particular case, it's not possible to pass them separately as proper arguments (it's for an flock invocation which doesn't accept arguments for a -c script).

Smth like this:

flock f.lock -c 'do_stuff '"${ARGS[@]}"'; do_other_stuff'

How do I quote them properly so that they are correctly parsed into a sequence of arguments, even if they contain spaces or Bash special syntax?

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
  • Aren't you simply looking for `flock f.lock -c "do_stuff $@; do_other_stuff"`? – tripleee Sep 22 '20 at 11:51
  • @tripleee that won't properly escape Bash special characters. `ARGS=("foo bar" 'baz\n'\' '$xyzzy'); echo "do_stuff ${ARGS[@]}; do_other_stuff"` -> `do_stuff foo bar baz\n' $xyzzy; do_other_stuff` -- not good for `eval`/`-c`. – ivan_pozdeev Sep 22 '20 at 12:45
  • 1
    Sticking `$@` inside a larger quoted string usually isn't what you want. `"foo $@ bar"` would give you one word per element in `$@`, but the first word would be prefixed with `foo ` and the last one suffixed with ` bar`. – chepner Sep 22 '20 at 12:57
  • In older versions of bash you can use `printf -v array_str '%q ' "${array[@]}"` for this, but I'm absolutely with @KamilCuk here: Simply don't. There's no reason to have `flock` start a new shell. – Charles Duffy Sep 22 '20 at 13:12
  • 1
    BTW, you might find the answer to [How to "just" lock a file](https://stackoverflow.com/questions/24388009/linux-flock-how-to-just-lock-a-file) informative, insofar as its answer provides a high-level wrapper around the practices that KamilCuk's answer advises. – Charles Duffy Sep 22 '20 at 20:41

2 Answers2

3

Don't! It is going to be error prone and give you pain. Just:

{
     flock 9
     do_stuff "${ARGS[@]}"
     do_other_stuff
} 9>f.lock

Anyway, split the operation into two:

  • first, safely transfer the environment to the subshell
  • then execute what you want to execute in a normal way

And it's just:

bash -c "$(declare -p ARGS)"'; do_stuff "${ARGS[@]}"; do_other_stuff'
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • But `flock` is a subprocess. How can it affect a descriptor in the parent process? It will only affect its own, inherited descriptor, within its own process. So the lock would only be held by `flock` while it's executing. While I need it to be held for the entire script. – ivan_pozdeev Sep 22 '20 at 15:52
  • 2
    `How can it affect a descriptor in the parent process? It will only affect its own, inherited descriptor,` Read `man 1 flock` and most importantly `man 2 flock`, it says: `Locks created by flock() are associated with an open file description (see open(2)). This means that duplicate file descriptors (created by, for example, fork(2) or dup(2)) refer to the same lock`. Inherited file descriptors refer to the same file and thus to the same lock - flock is a property of the file (only _identified_ by fd). – KamilCuk Sep 22 '20 at 16:06
1

Use Parameter transformation (new in Bash 4.4) with the Q (quote) operator which is specifically designed for this:

Q      The expansion is a string that is the value of parameter quoted
       in a format that can be reused as input.
$ ARGS=("foo bar" 'baz\n'\' '$xyzzy')

$ echo 'do_stuff '"${ARGS[*]@Q}"'; do_other_stuff'
do_stuff 'foo bar' 'baz\n'\''' '$xyzzy'; do_other_stuff

Note the use of * instead of the usual @ since the code needs to be a single argument. Using @ leads to erroneous behavior in some cases:

$ bash -xc "echo ${ARGS[@]@Q}; do_other_stuff"
+ echo 'foo bar'
foo bar

$ bash -xc "echo ${ARGS[*]@Q}; do_other_stuff"
+ echo 'foo bar' 'baz\n'\''' '$xyzzy'
foo bar baz\n' $xyzzy
+ do_other_stuff

You can even use this syntax for $*:

'do_stuff '"${*@Q}"' other args; do other stuff'
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152