$ @ кроме 1-го аргумента

36

Мне нужно написать сценарий оболочки, который работает следующим образом:

./myscript arg1 arg2_1 arg2_2 arg2_3 ....... arg2_#

внутри скрипта есть цикл for

for i in $@

Однако, как я знаю, $ @ включает в себя от $ 1 до $ ($ # - 1). Но для моей программы $ 1 заметно отличается от $ 2 $ 3 $ 4 и т. Д. Я хотел бы зациклить от $ 2 до конца ... Как мне этого добиться? Спасибо:)

user40780
источник

Ответы:

47

Во-первых, обратите внимание, что $@без кавычек не имеет смысла и не должно использоваться. $@должен использоваться только в кавычках ( "$@") и в контекстах списка.

for i in "$@" квалифицируется как контекст списка, но здесь, для обхода позиционных параметров, каноническая, наиболее переносимая и более простая форма:

for i
do something with "$i"
done

Теперь, чтобы перебрать элементы, начиная со второго, каноническим и наиболее переносимым способом является использование shift:

first_arg=$1
shift # short for shift 1
for i
do something with "$i"
done

После shiftэтого то, что раньше $1было удалено из списка (но мы его сохранили $first_arg), а то, что было раньше, $2теперь есть $1. Позиционные параметры были смещены 1 влево (используйте shift 2для смещения на 2 ...). Таким образом, по сути, наш цикл переходит от того, что раньше было вторым аргументом к последнему.

С bashzshи ksh93, но это все) альтернативой является:

for i in "${@:2}"
do something with "$i"
done

Но обратите внимание, что это не стандартный shсинтаксис, поэтому его не следует использовать в сценарии, который начинается с #! /bin/sh -.

В zshили yashвы также можете сделать:

for i in "${@[3,-3]}"
do something with "$i"
done

цикл с 3-го по 3-й последний аргумент.

В zsh, $@также известный как $argvмассив. Таким образом, чтобы вытолкнуть элементы из начала или конца массивов, вы также можете сделать:

argv[1,3]=() # remove the first 3 elements
argv[-3,-1]=()

( shiftтакже может быть написано 1=()в zsh)

В bash, вы можете назначить только $@элементы со setвстроенными, так что выкинуть 3 элемента с конца, это будет что-то вроде:

set -- "${@:1:$#-3}"

И чтобы перейти с 3-го на 3-е последнее:

for i in "${@:3:$#-5}"
do something with "$i"
done

POSIXly, чтобы вытолкнуть последние 3 элемента "$@", вам нужно использовать цикл:

n=$(($# - 3))
for arg do
  [ "$n" -gt 0 ] && set -- "$@" "$arg"
  shift
  n=$((n - 1))
done
Стефан Шазелас
источник
2
Альтернативная (и безобразная) возможность bash: косвенные переменные:for ((i=2; i<=$#; i++)); do something with "${!i}"; done
Гленн Джекман
Я больше знаком с этой версией, так как я больше знаком с c ++ :)
user40780
10

Я думаю, что вы хотите shiftвстроенный. Переименовывает $2в $1, $3в $2и т. Д.

Так:

shift
for i in "$@"; do
    echo $i
done
Джон
источник
Не могли бы вы объяснить более подробно, как мне добиться этого в цикле for? Спасибо.
user40780
1
Вы не - вы используете его перед входом в forцикл, затем вы просто циклически проходите через $ @. После shiftзвонка $ @ должно бытьarg2_1 arg2_2 arg2_3...
Джон
Однако у меня возникнет еще один вопрос: предположим, что я хочу выполнить цикл от $ 1 до $ ($ # - 2) (т.е. от arg_1 до arg_2 _ # - 1, кроме arg_2 _ #) ... Что мне делать?
user40780
2

Всегда есть подход пещерного человека:

first=1
for i
do
        if [ "$first" ]
        then
                first=
                continue
        fi
        something with "$i"
done

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

Скотт
источник
1

В bash вы также можете написать этот цикл с явной индексацией:

for ((i=2; i<=$#; ++i)); do
  process "${!i}"
done

Это перебирает все аргументы от второго до последнего. Если вы хотите исключить последний аргумент, просто сделайте это

for ((i=1; i<=$#-1; ++i)); do
  process "${!i}"
done

и если вы хотите принять любой другой аргумент, напишите его как

for ((i=1; i<=$#; i+=2)); do
  process "${!i}"
done

За этим стоит арифметическая версия forвстроенной функции в сочетании с числом аргументов$# и косвенной переменной${…} .

Одним из хороших приложений является то, что вы можете использовать это, чтобы решить, внутри цикла, будет ли данная опция принимать аргумент, следующий за ней, в качестве значения. Если это так, увеличивайте i(например, записывайте : $((++i))), чтобы использовать следующее значение, и пропустите его во время итерации.

MVG
источник