У меня есть сложная команда, которую я хотел бы сделать оболочкой / bash script. Я могу легко записать его в терминах $1
:
foo $1 args -o $1.ext
Я хочу иметь возможность передавать несколько имен ввода в script. Какой правильный способ сделать это?
И, конечно, я хочу обрабатывать имена файлов с пробелами в них.
Используйте "$@"
для представления всех аргументов:
for var in "$@"
do
echo "$var"
done
Это будет перебирать каждый аргумент и распечатывать его на отдельной строке. $@ведет себя как $*, за исключением того, что при цитировании аргументы разбиваются правильно, если в них есть пробелы:
sh test.sh 1 2 '3 4'
1
2
3 4
"$@"
является то, что он расширяется до нуля, когда нет позиционных параметров, тогда как "$*"
расширяется до пустой строки - и да, есть разница между отсутствием аргументов и одним пустым аргументом , Смотрите ${1:+"$@"} in
/ bin / sh` .
Перепишите теперь удаленный ответ 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.
Это обложка script, которая выполняет как delta
(думаю, заезд), так и
get
(думаю, выписка).
Различные аргументы, особенно -y
(причина, по которой вы внесли изменения)
будет содержать пробелы и новые строки.
Обратите внимание, что script датируется 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"
(Я бы, вероятно, не использовал бы бегство настолько тщательно в эти дни - это
не требуется с аргументом -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
доступен по запросу (email to firstname dot
lastname в gmail dot com).
Исходный код для al
невероятно прост:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
Это все. Это эквивалентно test.sh
script, который показал Роберт Гэмбл и может быть записан как функция оболочки (но функции оболочки не существовали в локальной версии оболочки Bourne, когда я впервые написал al
).
Также обратите внимание, что вы можете написать al
как простую оболочку script:
[ $# != 0 ] && printf "%s\n" "$@"
Условное значение необходимо, чтобы оно не выдавало вывода при отсутствии аргументов. Команда printf
создаст пустую строку с только аргументом строки формата, но программа C ничего не производит.
Обратите внимание, что ответ Роберт правильный, и он работает в sh
. Вы можете (переносимо) упростить его еще больше:
for i in "$@"
эквивалентно:
for i
I.e., вам ничего не нужно!
Тестирование ($
- это командная строка):
$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "$@"; do echo "$i"; done
a
b
spaces here
d
Впервые я прочитал об этом в среде программирования Unix Керниганом и Пайком.
В bash
, help for
документирует это:
for NAME [in WORDS ... ;] do COMMANDS; done
Если
'in WORDS ...;'
нет, тогда предполагается'in "$@"'
.
"$@"
, и как только вы поймете, for i
чего for i
, это не менее читабельно, чем for i in "$@"
.
Для простых случаев вы также можете использовать shift
.
Он рассматривает список аргументов как очередь, каждый shift
выдает первый аргумент,
число оставшихся аргументов уменьшается.
#this prints all arguments
while test $# -gt 0
do
echo $1
shift
done
shift
в цикле for, этот элемент все еще является частью массива, тогда как с этим shift
работает как ожидалось.
echo "$1"
чтобы сохранить интервал в значении строки аргумента. Кроме того, (незначительная проблема) это разрушительно: вы не можете повторно использовать аргументы, если вы удалили их все. Часто это не проблема - вы все равно обрабатываете список аргументов только один раз.
aparse() {
while [[ $# > 0 ]] ; do
case "$1" in
--arg1)
varg1=${2}
shift
;;
--arg2)
varg2=true
;;
esac
shift
done
}
aparse "$@"
[ $# != 0 ]
лучше. В приведенном выше примере #$
должно быть $#
как в while [[ $# > 0 ]] ...
Вы также можете обращаться к ним как к элементам массива, например, если вы не хотите перебирать их через все
argc=$#
argv=($@)
for (( j=0; j<argc; j++ )); do
echo ${argv[j]}
done