Как перебирать аргументы в Bash script

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

foo $1 args -o $1.ext

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

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

Ответ 1

Используйте "[email protected]" для представления всех аргументов:

for var in "[email protected]"
do
    echo "$var"
done

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

sh test.sh 1 2 '3 4'
1
2
3 4

Ответ 2

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

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

Смотрите также: ${1: + "[email protected]" } в /bin/sh

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

Итак, в чем разница между $*, [email protected], "$*" и "[email protected]"? Все они связаны с "всеми аргументами оболочки", но они делают разные вещи. Если некорректно, $* и [email protected] делают то же самое. Они рассматривают каждое слово (последовательность не-пробелов) как отдельный аргумент. Котируемые формы совершенно разные, но: "$*" рассматривает список аргументов как одну строку, разделенную пробелом, тогда как "[email protected]" обрабатывает аргументы почти так же, как они были указаны в командной строке. "[email protected]" вообще не расширяется, когда нет позиционных аргументов; "$*" расширяется до пустой строки — и да, есть разница, хотя это может быть трудно понять. См. Дополнительную информацию ниже, после введения (нестандартной) команды al.

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

Пример:

    $ 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 "[email protected]"
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"
    $

[Добавлено: И теперь, чтобы показать разницу между различными обозначениями "[email protected]", вот еще один пример:

$ cat xx.sh
set -x
al [email protected]
al $*
al "$*"
al "[email protected]"
$ 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" "[email protected]"

Условное значение необходимо, чтобы оно не выдавало вывода при отсутствии аргументов. Команда printf создаст пустую строку с только аргументом строки формата, но программа C ничего не производит.

Ответ 3

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

for i in "[email protected]"

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

for i

I.e., вам ничего не нужно!

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

$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "[email protected]"; do echo "$i"; done
a
b
spaces here
d

Впервые я прочитал об этом в среде программирования Unix Керниганом и Пайком.

В bash, help for документирует это:

for NAME [in WORDS ... ;] do COMMANDS; done

Если 'in WORDS ...;' нет, тогда предполагается 'in "[email protected]"'.

Ответ 4

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

#this prints all arguments
while test $# -gt 0
do
    echo "$1"
    shift
done

Ответ 5

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

argc=$#
argv=("[email protected]")

for (( j=0; j<argc; j++ )); do
    echo "${argv[j]}"
done

Ответ 6

aparse() {
while [[ $# > 0 ]] ; do
  case "$1" in
    --arg1)
      varg1=${2}
      shift
      ;;
    --arg2)
      varg2=true
      ;;
  esac
  shift
done
}

aparse "[email protected]"