2017-02-10 2 views
1

Я работаю над тем, чтобы привыкнуть к сценариям оболочки и сталкивался с поведением, которое я нашел интересным и необъяснимым. В следующем коде первый цикл будет выполняться правильно, а второй - нет.Синтаксис Bash for loop

declare letters=(a b c d e f g) 
for i in {0..7}; do 
    echo ${letters[i]} 
done 
for i in {0..${#letters[*]}}; do 
    echo ${letters[i]} 
done 

второй по результатам цикла в следующей ошибки:

syntax error: operand expected (error token is "{0..7}") 

Что меня смущает, что это ${#letters[*]} явно получение оценивается, правильно, на номер 7. Но, несмотря на это код, даже если не удается мы просто видели, что тот же цикл с {0..7} работает отлично.

В чем причина этого?

Я запускаю OS X 10.12.2, версию GNU bash 3.2.57.

+0

Не уверен насчет объяснения этого Но когда вы используете {0..7} это становится расширена до 1 2 3 4 5 ... и т.д. Но если вы {0 .. $ {# буквы [* ]} это расширяется до строки «{0..7}», которую вы не можете перебирать по – Yarden

ответ

3

Расширение скобки происходит перед расширением параметра, поэтому вы не можете использовать переменную как часть диапазона.

Разверните массив в список значений:

for letter in "${letters[@]}"; do 
    echo "$letter" 
done 

Или расширить индексы массива в списке:

for i in ${!letters[@]}; do 
    echo "${letters[i]}" 
done 

Как уже упоминалось в комментариях (спасибо), эти два подходы также допускают разреженные массивы; вы не всегда можете предположить, что массив определяет значение для каждые индекс между 0 и ${#letters[@]}.

+1

. Они также вмещают разреженные массивы; вы не всегда можете предположить, что массив определяет значение для * каждого * индекса между 0 и '$ {# letters [@]}'. – chepner

+0

@chepner спасибо, отредактировал это в. –

5

Расширение кронштейна происходит до расширения параметра (см. РАСШИРЕНИЯ в man bash), поэтому оно работает только для литералов. Другими словами, вы не можете использовать расширение скобки с переменными.

Вы можете использовать цикл C-стиль:

for ((i=0; i<${#letters[@]}; i++)) ; do 
    echo ${letters[i]} 
done 

или внешнюю команду как seq:

for i in $(seq 1 ${#letters[@]}) ; do 
    echo ${letters[i-1]} 
done 

Но вы обычно не нужны индексы, а не один цикл по элементам сами, см. ответ @ TomFenech ниже. Он также показывает другой способ получения списка индексов.

Обратите внимание, что это должно быть {0..6}, а не 7.

Смежные вопросы