Как перебирать аргументы в скрипте Bash - PullRequest
785 голосов
/ 01 ноября 2008

У меня есть сложная команда, из которой я хотел бы сделать скрипт shell / bash. Я могу написать это в терминах $1 легко:

foo $1 args -o $1.ext

Я хочу иметь возможность передавать несколько входных имен в сценарий. Какой правильный способ сделать это?

И, конечно же, я хочу обработать имена файлов с пробелами в них.

Ответы [ 6 ]

1281 голосов
/ 01 ноября 2008

Используйте "$@" для представления всех аргументов:

for var in "$@"
do
    echo "$var"
done

Это будет перебирать каждый аргумент и выводить его на отдельной строке. $ @ ведет себя как $ * за исключением того, что при кавычках аргументы разбиваются правильно, если в них есть пробелы:

sh test.sh 1 2 '3 4'
1
2
3 4
218 голосов
/ 02 ноября 2008

Переписать теперь удаленный ответ по VonC .

Сжатый ответ Роберта Гэмбла касается непосредственно вопроса. Это усиливает некоторые проблемы с именами файлов, содержащими пробелы.

См. Также: $ {1: + "$ @"} в / bin / sh

Основной тезис: "$@" правильно, а $* (без кавычек) почти всегда неправильно. Это потому, что "$@" работает нормально, когда аргументы содержат пробелы, и работает так же, как $*, когда они этого не делают. В некоторых случаях "$*" тоже нормально, но обычно "$@" (но не всегда) работает в одних и тех же местах. Без кавычек $@ и $* эквивалентны (и почти всегда неверны).

Итак, в чем разница между $*, $@, "$*" и "$@"? Все они связаны со «всеми аргументами оболочки», но делают разные вещи. В кавычках $* и $@ делают то же самое. Они рассматривают каждое слово (последовательность непробельных символов) как отдельный аргумент. Однако формы в кавычках совершенно разные: "$*" обрабатывает список аргументов как одну строку, разделенную пробелами, тогда как "$@" обрабатывает аргументы почти точно так же, как они были указаны в командной строке. "$@" вообще ничего не расширяется, когда нет позиционных аргументов; "$*" раскрывается в пустую строку & mdash; и да, есть разница, хотя это может быть трудно понять. См. Дополнительную информацию ниже, после введения (нестандартной) команды al.

Вторичный тезис: если вам нужно обработать аргументы с пробелами, а затем передать их другим командам, тогда вам иногда нужно нестандартное инструменты для оказания помощи. (Или вы должны использовать массивы, осторожно: "${array[@]}" ведет себя аналогично "$@".)

* +1047 * Пример: * 1 049 * $ 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 (думаю 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" (я бы, наверное, не использовал побег так тщательно в наши дни - это например, не требуется с аргументом -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'.
] Хм, это довольно длинный ответ - возможно, exegesis - лучший термин. Исходный код escape доступен по запросу (электронная почта на имя, точка фамилия в gmail точка ком). Исходный код 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 выдаст пустую строку только с аргументом строки формата, но программа на Си ничего не выдаст.
116 голосов
/ 01 января 2010

Обратите внимание, что ответ Роберта верен, и он также работает в sh. Вы можете (портативно) упростить это еще больше:

for i in "$@"

эквивалентно:

for i

То есть, тебе ничего не нужно!

Тестирование ($ - командная строка):

$ 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 "$@"'.

52 голосов
/ 29 октября 2012

Для простых случаев вы также можете использовать shift. Он обрабатывает список аргументов как очередь, каждый shift выбрасывает первый аргумент, номер каждого оставленного аргумента уменьшается.

#this prints all arguments
while test $# -gt 0
do
    echo $1
    shift
done
12 голосов
/ 30 октября 2017

Вы также можете обращаться к ним как к элементам массива, например, если вы не хотите выполнять итерацию по всем из них

argc=$#
argv=($@)

for (( j=0; j<argc; j++ )); do
    echo ${argv[j]}
done
3 голосов
/ 14 января 2016
aparse() {
while [[ $# > 0 ]] ; do
  case "$1" in
    --arg1)
      varg1=${2}
      shift
      ;;
    --arg2)
      varg2=true
      ;;
  esac
  shift
done
}

aparse "$@"
...