Shell Script – Passing Arguments with Spaces and Quotes

argumentsfunctionquotingshell-scriptwhitespace

The following works great on the command-line:

$ ffmpeg -i input.m4a -metadata 'title=Spaces and $pecial char'\''s' output.m4a

How do I parameterize this command and use it in a script/function? I would like to add multiple metadata tags like this:

$ set-tags.sh -metadata 'tag1=a b c' -metadata 'tag2=1 2 3'

update:

I simplified my question a little too much. I actually want to call a script that calls a script with with the parameterized command in it.

This is my exact use case:

This function converts files to audio-book format (defined in .profile):

# snippet of .profile
convert_to_m4b () {
    FILE="$1"
    BASENAME=${FILE%.*}; shift

    ffmpeg -i "$FILE" -vn -ac 1 -ar 22050 -b:a 32k "$@" tmp.m4a &&
    mv tmp.m4a "$BASENAME.m4b"
}; export -f convert_to_m4b

Function convert_to_m4b is called from download-and-convert.sh:

#/bin/sh
 MP3_URL=$1; shift
FILENAME=$1; shift

if [ ! -f "${FILENAME}.mp3" ]; then
    curl --location --output "${FILENAME}.mp3" "$MP3_URL"
fi

convert_to_m4b "${FILENAME}.mp3" "$@"

Download-and-convert.sh is called from process-all.sh:

#/bin/sh
download-and-convert.sh http://1.mp3 'file 1' -metadata 'title=title 1' -metadata 'album=album 1'
download-and-convert.sh http://2.mp3 'file 2' -metadata 'title=title 2' -metadata 'album=album 2'
...
...
download-and-convert.sh http://3.mp3 'file N' -metadata 'title=title N' -metadata 'album=album N'

I get this error from ffmpeg:

[NULL @ 00000000028fafa0] Unable to find a suitable output format for ''@''
'@': Invalid argument

"$@" works if I inline convert_to_m4b in download-and-convert.sh instead of calling the function.


The following does not work because the quotes are lost, causing arguments with spaces to be incorrectly split up:

#/bin/sh
ffmpeg -i input.m4a $@ output.m4a

I have tried various methods of quoting the $@, but this ends up quoting '-metadata' as well, so the command line argument is not properly recognized.

I guess I would like to only surround each argument with quotes if that argument was quoted to begin with. This seems difficult to do because bash strips the quotes before being passing arguments to the script/function.

Or is there a better method of relaying the -metadata arguments? (like environment variables or files)

Best Answer

"$@" does exactly what you want provided that you use it consistently. Here's a little experiment for you:

  • script1.sh:

    #! /bin/sh
    ./script2.sh "$@"
    
  • script2.sh:

    #! /bin/sh
    ./script3.sh "$@"
    
  • script3.sh:

    #! /bin/sh
    printf '|%s|\n' "$@"
    

With this the arguments stay unmolested all the way down:

$ ./script1.sh -i input.m4a -metadata "title=Spaces and \$pecial char's" output.m4a
|-i|
|input.m4a|
|-metadata|
|title=Spaces and $pecial char's|
|output.m4a|
Related Question