How to pass bash script arguments to a subshell

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}" "$@")"


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[*]}

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

Tags:

Bash