Переписать из теперь удален ответ по VonC .
Сжатый ответ Роберта Гэмбла касается непосредственно вопроса. Это усиливает некоторые проблемы с именами файлов, содержащими пробелы.
Смотрите также: $ {1: + "$ @"} в / bin / sh
Основной тезис: "$@"
правильно и $*
(без кавычек) почти всегда неправильно. Это потому, что "$@"
работает нормально, когда аргументы содержат пробелы, и работает так же, как и $*
когда их нет. В некоторых случаях "$*"
это тоже нормально, но "$@"
обычно (но не всегда) работает в одних и тех же местах. Без кавычек $@
и $*
эквивалентны (и почти всегда неверны).
Итак, в чем разница между $*
, $@
, "$*"
и "$@"
? Все они связаны со «всеми аргументами оболочки», но делают разные вещи. Когда без кавычек,$*
и $@
делать то же самое. Они рассматривают каждое «слово» (последовательность без пробелов) как отдельный аргумент. Однако формы в кавычках совершенно разные: "$*"
обрабатывает список аргументов как одну строку, разделенную пробелами, тогда "$@"
как аргументы обрабатывают почти точно так, как они были указаны в командной строке.
"$@"
вообще ничего не расширяется, когда нет позиционных аргументов; "$*"
расширяется до пустой строки - и да, есть разница, хотя это может быть трудно воспринимать. См. Дополнительную информацию ниже, после введения (нестандартной) команды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
$
Почему это не работает? Это не работает, потому что оболочка обрабатывает кавычки, прежде чем она расширяет переменные. Итак, чтобы оболочка обратила внимание на кавычки $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'
. Тем не менее, оболочки настоятельно рекомендуют не использовать пробелы, символы новой строки или табуляции нигде в именах путей. Именно поэтому стандартные имена файлов Unix не содержат пробелов и т. Д.
При работе с именами файлов, которые могут содержать пробелы и другие неприятные символы, вы должны быть чрезвычайно осторожны, и я давно обнаружил, что мне нужна программа, которая не является стандартной для Unix. Я называю этоescape
(версия 1.1 датирована 1989-08-23T16: 01: 45Z).
Вот пример escape
использования - с системой управления SCCS. Это сценарий обложки, который выполняет как delta
(думайте о регистрации ), так и
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
, у меня есть программа , которая сделала удаленное выполнение оболочки, и что необходимо , чтобы избежать вывода 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
$
Обратите внимание, что ничего не сохраняет оригинальные пробелы между *
и */*
в командной строке. Также обратите внимание, что вы можете изменить «аргументы командной строки» в оболочке, используя:
set -- -new -opt and "arg with space"
Это устанавливает 4 опции, ' -new
', ' -opt
', ' and
' и ' arg with space
'.
]
Хм, это довольно длинный ответ - возможно, толкование - лучший термин. Исходный код escape
доступен по запросу (электронная почта на имя, точка, фамилия на gmail, точка com). Исходный код al
невероятно прост:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
Это все. Он эквивалентен test.sh
сценарию, который показал Роберт Гэмбл, и может быть написан как функция оболочки (но функции оболочки не существовали в локальной версии оболочки Bourne, когда я впервые писалal
).
Также обратите внимание, что вы можете написать al
в виде простого сценария оболочки:
[ $# != 0 ] && printf "%s\n" "$@"
Условное условие необходимо для того, чтобы оно не выдавало выходных данных при отсутствии аргументов. Команда printf
выдаст пустую строку только с аргументом строки формата, но программа на Си ничего не выдаст.