用VonC重写现在删除的answer。
Robert Gamble 的简洁回答直接解决了这个问题。
这一个放大了包含空格的文件名的一些问题。
另请参阅:${1:+"$@"} in /bin/sh
基本论点: "$@" 是正确的,$*(未引用)几乎总是错误的。
这是因为 "$@" 在参数包含空格时可以正常工作,并且
当他们不这样做时,与$* 的工作方式相同。
在某些情况下,"$*" 也可以,但"$@" 通常(但不是
总是)在同一个地方工作。
未加引号的$@ 和$* 是等价的(而且几乎总是错误的)。
那么,$*、$@、"$*" 和 "$@" 之间有什么区别?它们都与“shell 的所有参数”有关,但它们做不同的事情。当不加引号时,$* 和 $@ 做同样的事情。他们将每个“单词”(非空白序列)视为一个单独的参数。但是,带引号的形式完全不同:"$*" 将参数列表视为单个空格分隔的字符串,而"$@" 将参数几乎与在命令行中指定时完全相同。
当没有位置参数时,"$@" 完全没有扩展; "$*" 扩展为一个空字符串——是的,有区别,尽管很难察觉。
在引入(非标准)命令al 之后,请参阅下面的更多信息。
次要论文:如果您需要处理带有空格的参数,然后
将它们传递给其他命令,那么您有时需要非标准
辅助工具。 (或者您应该谨慎使用数组:"${array[@]}" 的行为类似于 "$@"。)
例子:
$ mkdir "my dir" anotherdir
$ ls
anotherdir my dir
$ cp /dev/null "my dir/my file"
$ cp /dev/null "anotherdir/myfile"
$ ls -Fltr
total 0
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir/
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir/
$ ls -Fltr *
my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ ls -Fltr "./my dir" "./anotherdir"
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ var='"./my dir" "./anotherdir"' && echo $var
"./my dir" "./anotherdir"
$ ls -Fltr $var
ls: "./anotherdir": No such file or directory
ls: "./my: No such file or directory
ls: dir": No such file or directory
$
为什么这不起作用?
它不起作用,因为外壳在扩展之前处理引号
变量。
因此,要让 shell 注意到 $var 中嵌入的引号,
你必须使用eval:
$ eval ls -Fltr $var
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$
当您有诸如“He said,
"Don't do this!"”之类的文件名(带有引号、双引号和空格)时,这会变得非常棘手。
$ cp /dev/null "He said, \"Don't do this!\""
$ ls
He said, "Don't do this!" anotherdir my dir
$ ls -l
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 15:54 He said, "Don't do this!"
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir
$
外壳(所有外壳)并不容易处理此类
东西,所以(很有趣)许多 Unix 程序并不能很好地处理
处理它们。
在 Unix 上,文件名(单个组件)可以包含任何字符,除了
斜线和 NUL '\0'。
但是,shell 强烈建议不要使用空格、换行符或制表符
路径名称中的任何位置。
这也是为什么标准 Unix 文件名不包含空格等原因。
在处理可能包含空格和其他字符的文件名时
麻烦的字符,你必须非常小心,我发现
很久以前,我需要一个在 Unix 上不标准的程序。
我称之为escape(1.1 版的日期为 1989-08-23T16:01:45Z)。
以下是使用中的escape 示例 - 与 SCCS 控制系统一起使用。
这是一个封面脚本,它同时执行 delta(想想 check-in)和
get(想想退房)。
各种争论,尤其是-y(你做出改变的原因)
将包含空格和换行符。
请注意,该脚本可追溯到 1992 年,因此它使用反引号而不是
$(cmd ...) 符号并且不在第一行使用#!/bin/sh。
: "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
# Delta and get files
# Uses escape to allow for all weird combinations of quotes in arguments
case `basename $0 .sh` in
deledit) eflag="-e";;
esac
sflag="-s"
for arg in "$@"
do
case "$arg" in
-r*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
-e) gargs="$gargs `escape \"$arg\"`"
sflag=""
eflag=""
;;
-*) dargs="$dargs `escape \"$arg\"`"
;;
*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
esac
done
eval delta "$dargs" && eval get $eflag $sflag "$gargs"
(这些天我可能不会那么彻底地使用 escape - 它是
例如,-e 参数不需要 - 但总的来说,这是
我使用escape 的更简单的脚本之一。)
escape 程序只是输出它的参数,就像echo
确实如此,但它确保参数受到保护以便与
eval(eval 的一级;我确实有一个执行远程 shell 的程序
执行,并且需要转义escape的输出)。
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
我有另一个名为 al 的程序,它每行列出一个参数
(它甚至更古老:1.1 版,日期为 1987-01-27T14:35:49)。
它在调试脚本时最有用,因为它可以插入到
命令行查看实际传递给命令的参数。
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
[添加:
现在来展示各种"$@" 符号之间的区别,这里还有一个例子:
$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ sh xx.sh * */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$
请注意,命令行上的* 和*/* 之间的原始空白没有任何保留。另外,请注意,您可以使用以下命令更改 shell 中的“命令行参数”:
set -- -new -opt and "arg with space"
这设置了 4 个选项,“-new”、“-opt”、“and”和“arg with space”。
]
嗯,这是一个相当长的答案 - 也许exegesis 是更好的术语。
escape 的源代码可根据要求提供(电子邮件至名点
gmail dot com 的姓氏)。
al 的源代码非常简单:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
就是这样。它相当于 Robert Gamble 展示的 test.sh 脚本,可以写成 shell 函数(但当我第一次写 al 时,本地版本的 Bourne shell 中不存在 shell 函数)。
还请注意,您可以将al 编写为简单的shell 脚本:
[ $# != 0 ] && printf "%s\n" "$@"
条件是必需的,以便在不传递任何参数时不产生输出。 printf 命令将产生一个只有格式字符串参数的空行,但 C 程序什么也不产生。