36

I would like to define a simple abbreviation of a call to gs (ghostscript) via a shell script. The first argument(s) give all the files that should be merged, the last one gives the name of the output file. Obviously, the following does not work (it's just for showing the goal):

#!/bin/sh
gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOUTPUTFILE=$last $1 $2 ...

How can this be done?

One would typically call this script via myscript infile1.pdf infile2.pdf ... outfile.pdf or myscript *.pdf outfile.pdf.

Marius Hofert
  • 5,873
  • 10
  • 36
  • 91

3 Answers3

57

The bash variables $@ and $* expand into the list of command line arguments. Generally, you will want to use "$@" (that is, $@ surrounded by double quotes). This will do the right thing if someone passes your script an argument containing whitespace.

So if you had this in your script:

outputfile=$1
shift
gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOUTPUTFILE=$outputfile "$@"

And you called your script like this:

myscript out.pdf foo.ps bar.ps "another file.ps"

This would expand to:

gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOUTPUTFILE=out.pdf foo.ps bar.ps "another file.ps"

Read the "Special Parameters" section of the bash man page for more information.

larsks
  • 194,279
  • 34
  • 297
  • 301
  • Thanks larsks. Is it possible to have the output file name as the *last* (not the first) argument? – Marius Hofert Apr 24 '12 at 23:32
  • 3
    Marius Hofert: You can't easily shift off the last element, so that is a little more tricky. Something like this: `outputfile=${@: -1}; args=("${@:1:$((${#@}-1))}")`. You should accept this larsks's answer. – jordanm Apr 25 '12 at 01:43
  • You *can*, but it's a little ugly. Idelic has an example in his answer. You end up with a lot more cruft in your code. – larsks Apr 25 '12 at 02:50
  • Hmm.. can I use this with the default variable as well? `echo ${@-'default-val'}` That's my best guess at the code but it ignores the default variable when I test it. – Costa Oct 06 '18 at 23:19
  • I would need to see your actual code to answer your question (for example, using any variant of $@ outside of double quotes doesn't really make sense, but I can't tell if you're doing that or not). I suggest you just open a new question and include an example of what you're trying to do. – larsks Oct 07 '18 at 02:06
21

To pass the output file as the last argument, use an array:

ARGS=("$@")
# Get the last argument
outputfile=${ARGS[-1]}
# Drop it from the array
unset ARGS[${#ARGS[@]}-1]

exec gs ... -sOUTPUTFILE=$outputfile "${ARGS[@]}"

Before version 4, bash didn't allow negative subscripts in arrays (and produced the error reported by Marius in the comments), so if you're using 3.x you need to use the much uglier

outputfile=${ARGS[${#ARGS[@]}-1]}

This works for bash 4.x as well.

Idelic
  • 12,910
  • 4
  • 32
  • 37
  • I obtain: gsMerge infile1.pdf infile2.pdf all.pdf /usr/bin/gsMerge: line 4: ARGS: bad array subscript **** Unable to open the initial device, quitting. – Marius Hofert Apr 25 '12 at 05:59
  • @Marius: You're probably using an old version of `bash`. I updated the answer to cover that case. – Idelic Apr 25 '12 at 14:48
  • Just to add: On Mac OS X 10.10.3, I updated `bash` to 4.3.33 but still needed to use `outputfile=${ARGS[${#ARGS[@]}-1]}` for it to work. In Debian Linux with bash 4.3.30 `outputfile=${ARGS[-1]}` worked. – Marius Hofert Jun 05 '15 at 12:06
9

To access the last argument, in addition to Idelic's answer above, you can also do:

echo "${@: $#}"

This reads all of the arguments and prints them starting from the last one. This way, you can also access the N last arguments, for example for the last three arguments:

echo "${@: $#-2}"

$ ./script "what    does" "this  script" "do" "?"
this  script do ?
Ran Lottem
  • 416
  • 5
  • 14
  • Wow, this is awesome. Where is this defined in the manual? And is there a range version of this? – i336_ May 07 '17 at 01:40
  • See this answer: [link](https://stackoverflow.com/questions/1335815/how-to-slice-an-array-in-bash) about splicing arrays, the above answer is simply using that to slice the array of input arguments. – Ran Lottem May 08 '17 at 11:29