I have a complex command that I'd like to make a shell/bash script of. I can write it in terms of $1
easily:
foo $1 args -o $1.ext
I want to be able to pass multiple input names to the script. What's the right way to do it?
And, of course, I want to handle filenames with spaces in them.
Note that Robert's answer is correct, and it works in
sh
as well. You can (portably) simplify it even further:is equivalent to:
I.e., you don't need anything!
Testing (
$
is command prompt):I first read about this in Unix Programming Environment by Kernighan and Pike.
In
bash
,help for
documents this:Rewrite of a now-deleted answer by VonC.
Robert Gamble's succinct answer deals directly with the question. This one amplifies on some issues with filenames containing spaces.
See also: ${1:+"$@"} in /bin/sh
Basic thesis:
"$@"
is correct, and$*
(unquoted) is almost always wrong. This is because"$@"
works fine when arguments contain spaces, and works the same as$*
when they don't. In some circumstances,"$*"
is OK too, but"$@"
usually (but not always) works in the same places. Unquoted,$@
and$*
are equivalent (and almost always wrong).So, what is the difference between
$*
,$@
,"$*"
, and"$@"
? They are all related to 'all the arguments to the shell', but they do different things. When unquoted,$*
and$@
do the same thing. They treat each 'word' (sequence of non-whitespace) as a separate argument. The quoted forms are quite different, though:"$*"
treats the argument list as a single space-separated string, whereas"$@"
treats the arguments almost exactly as they were when specified on the command line."$@"
expands to nothing at all when there are no positional arguments;"$*"
expands to an empty string — and yes, there's a difference, though it can be hard to perceive it. See more information below, after the introduction of the (non-standard) commandal
.Secondary thesis: if you need to process arguments with spaces and then pass them on to other commands, then you sometimes need non-standard tools to assist. (Or you should use arrays, carefully:
"${array[@]}"
behaves analogously to"$@"
.)Example:
Why doesn't that work? It doesn't work because the shell processes quotes before it expands variables. So, to get the shell to pay attention to the quotes embedded in
$var
, you have to useeval
:This gets really tricky when you have file names such as "
He said, "Don't do this!"
" (with quotes and double quotes and spaces).The shells (all of them) do not make it particularly easy to handle such stuff, so (funnily enough) many Unix programs do not do a good job of handling them. On Unix, a filename (single component) can contain any characters except slash and NUL
'\0'
. However, the shells strongly encourage no spaces or newlines or tabs anywhere in a path names. It is also why standard Unix file names do not contain spaces, etc.When dealing with file names that may contain spaces and other troublesome characters, you have to be extremely careful, and I found long ago that I needed a program that is not standard on Unix. I call it
escape
(version 1.1 was dated 1989-08-23T16:01:45Z).Here is an example of
escape
in use - with the SCCS control system. It is a cover script that does both adelta
(think check-in) and aget
(think check-out). Various arguments, especially-y
(the reason why you made the change) would contain blanks and newlines. Note that the script dates from 1992, so it uses back-ticks instead of$(cmd ...)
notation and does not use#!/bin/sh
on the first line.(I would probably not use escape quite so thoroughly these days - it is not needed with the
-e
argument, for example - but overall, this is one of my simpler scripts usingescape
.)The
escape
program simply outputs its arguments, rather likeecho
does, but it ensures that the arguments are protected for use witheval
(one level ofeval
; I do have a program which did remote shell execution, and that needed to escape the output ofescape
).I have another program called
al
that lists its arguments one per line (and it is even more ancient: version 1.1 dated 1987-01-27T14:35:49). It is most useful when debugging scripts, as it can be plugged into a command line to see what arguments are actually passed to the command.[Added: And now to show the difference between the various
"$@"
notations, here is one more example:Notice that nothing preserves the original blanks between the
*
and*/*
on the command line. Also, note that you can change the 'command line arguments' in the shell by using:This sets 4 options, '
-new
', '-opt
', 'and
', and 'arg with space
'.]
Hmm, that's quite a long answer - perhaps exegesis is the better term. Source code for
escape
available on request (email to firstname dot lastname at gmail dot com). The source code foral
is incredibly simple:That's all. It is equivalent to the
test.sh
script that Robert Gamble showed, and could be written as a shell function (but shell functions didn't exist in the local version of Bourne shell when I first wroteal
).Also note that you can write
al
as a simple shell script:The conditional is needed so that it produces no output when passed no arguments. The
printf
command will produce a blank line with only the format string argument, but the C program produces nothing.You can also access them as an array elements, for example if you don't want to iterate through all of them
Use
"$@"
to represent all the arguments:This will iterate over each argument and print it out on a separate line. $@ behaves like $* except that when quoted the arguments are broken up properly if there are spaces in them:
For simple cases you can also use
shift
. It treats argument list like a queue, eachshift
throws the first argument out, number of each argument that is left is decremented.