The Computer Oracle

How to pass bash script arguments to a subshell

--------------------------------------------------
Rise to the top 3% as a developer or hire one of them at Toptal: https://topt.al/25cXVn
--------------------------------------------------

Music by Eric Matyas
https://www.soundimage.org
Track title: Realization

--

Chapters
00:00 How To Pass Bash Script Arguments To A Subshell
00:51 Accepted Answer Score 19
01:51 Answer 2 Score 4
02:31 Answer 3 Score 3
03:09 Answer 4 Score 2
04:03 Answer 5 Score 2
04:25 Thank you

--

Full question
https://superuser.com/questions/403263/h...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#bash

#avk47



ACCEPTED ANSWER

Score 19


Bash's printf command has a feature that'll quote/escape/whatever a string, so as long as both the parent and subshell are actually bash, this should work:

[Edit: as siegi pointed out in a comment, if you do this the obvious way there's a problem when no arguments are supplied, where it acts like there actually was a single empty argument. I've added a workaround below, wrapping the format string with ${1+}, which only includes the format string if the first argument is defined. It's a bit of a kluge, but it does work.]

#!/bin/bash

quoted_args="$(printf "${1+ %q}" "$@")" # Note: this will have a leading space before the first arg
# echo "Quoted args:$quoted_args" # Uncomment this to see what it's doing
bash -c "other_tool -a -b$quoted_args"

Note that you can also do it in a single line: bash -c "other_tool -a -b$(printf "${1+ %q}" "$@")"




ANSWER 2

Score 4


None of the solutions work well. Just pass x/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\" as parameter and they fail.

Here is a simple wrapper that handles every case. Note how it escapes each argument twice.

#!/usr/bin/env bash
declare -a ARGS
COUNT=$#
for ((INDEX=0; INDEX<COUNT; ++INDEX))
do
    ARG="$(printf "%q" "$1")"
    ARGS[INDEX]="$(printf "%q" "$ARG")"
    shift
done

ls -l ${ARGS[*]}



ANSWER 3

Score 3


It's failing because you're coercing an array (the positional parameters) into a string. "$@" is magical because it gives you each separate paramter as a properly quoted string. Adding additional text breaks the magic: "blah $@" is just a single string.

This may get you closer:

cmd="other_tool -a -b"
for parm in "$@"; do cmd+=" '$parm'"; done
adb sh -c "$cmd"

Of course, any parameter that contains a single quote will cause trouble.




ANSWER 4

Score 2


Change $@ to $*. I did a small local test and it works in my case.

#!/bin/sh
bash -c "echo $*"
bash -c "echo $@"

Saving as test.sh and making it executable gives

$ ./test.sh foo bar
foo bar
foo

There is a subtle difference between $* and $@, as you can see. See e.g. http://ss64.com/bash/syntax-parameters.html


For the follow-up question in the comments: you need to escape e.g. white-space "twice" to pass a string with a separator as a combined argument, e.g. with test.sh modified to a wc wrapper:

#!/bin/sh
bash -c "wc $*"

This works:

$ touch test\ file
$ ./test.sh -l "test\ file"
0 test file

but:

$ ./test.sh -l "test file"
wc: test: No such file or directory
wc: file: No such file or directory
0 total



ANSWER 5

Score 2


Ok, more explanashons:

$ cat /tmp/test.sh
#! /bin/bash

echo '$@='"$@"

set -v # "debug"

sh -c 'echo $@' "$@" # gremlins steal the first option

sh -c 'echo $@' -- "$@" # We defend the option with two knifes

$ bash -e /tmp/test.sh first second third
$@=first second third

sh -c 'echo $@' "$@" # gremlins steal the first option
second third

sh -c 'echo $@' -- "$@" # We defend the option with two knifes
first second third