Для цикла с алфавитом
Это отлично работает на OSX
#!/bin/bash
chars=( {a..z} )
n=3
for ((i=0; i<n; i++))
do
echo "${chars[i]}"
done
Но когда я запускаю его в Ubuntu, я получаю следующую ошибку.
ForLoopAlphabetTest.sh: 2: ForLoopAlphabetTest.sh: Syntax error: "(" unexpected
Я не могу решить проблему. Какие-либо предложения?
2 ответа
Предположительно, вы запускаете скрипт как:
sh ForLoopAlphabetTest.sh
В Ubuntu sh
связан с dash
; как dash
не имеет понятия о массивах, вы получаете синтаксическую ошибку для (
,
Скрипт отлично работает на bash
так что было бы хорошо, если бы вы запускали его как bash
аргумент:
bash ForLoopAlphabetTest.sh
Теперь у вас есть bash
на сценарий, чтобы вы могли сделать сценарий исполняемым (chmod u+x ForLoopAlphabetTest.sh
) и запустите его как:
/path/to/ForLoopAlphabetTest.sh
или из каталога скрипта:
./ForLoopAlphabetTest.sh
Также обратите внимание, что ваш скрипт содержит расширение скобки {a..z}
и C-стиль for
построить: for (( ... ))
которые также не поддерживаются dash
; поэтому, если ваша цель - переносимость, вы должны посмотреть на POSIX sh
только синтаксис.
В вашем сценарии используются три функции оболочки Bash, которые предоставляются не всеми оболочками в стиле Борна. Как говорит Heemayl, вы можете просто запустить этот скрипт с bash
вместо sh
, Ваша строка хэшбэнг вверху (#!/bin/bash
) указывает bash
но эффективен, только если вы выполняете сценарий, как объяснил Химайл. Если вы передадите имя скрипта sh
, sh
не будет автоматически звонить bash
, но просто запустит скрипт. Это потому, что когда ваш скрипт действительно запущен, строка hashbang не имеет никакого эффекта.
Другой вариант, если вам нужно написать полностью переносимые скрипты, которые не зависят от функций Bash, - это изменить ваш скрипт так, чтобы он работал без них. Используемые вами функции Bash:
- Массив. Это было первым, так что именно это и привело к ошибке, когда вы пытались запустить скрипт с помощью оболочки Dash. Выражение в скобках
( {a..z} )
, который вы назначаетеchars
создает массив и${chars[i]}
, который появляется в вашем цикле, индексирует в нем. - Брекет-расширение. В Баше, а также во многих других оболочках,
{a..z}
расширен доa b c d e f g h i j k l m n o p q r s t u v w x y z
, Тем не менее, это не универсальная (или стандартизированная) функция оболочек в стиле Борна, и Dash не поддерживает ее. - C-стиль альтернативный
for
Синтаксис Хотя он основан на арифметическом расширении, которое само по себе не является специфическим для Bash (хотя в некоторых очень старых, не POSIX-совместимых оболочках его тоже нет), стиль Cfor
Цикл является Bash-ism и не является широко переносимым для других оболочек.
Bash широко доступен, особенно в системах GNU/Linux, таких как Ubuntu, и (как вы видели) также доступен в macOS и многих других системах. Учитывая, сколько вы используете специфичных для Bash функций, вы можете просто захотеть использовать их и просто убедиться, что вы используете Bash (или какую-либо другую оболочку, которая поддерживает используемые вами функции) при запуске ваших сценариев.
Однако вы можете заменить их переносимыми конструкциями, если хотите. Массив и C-стиль for
петли легко заменить; генерация диапазона букв без расширения фигурных скобок (и без жесткого кодирования их в вашем скрипте) - это немного сложная часть.
Во-первых, вот скрипт, который печатает все строчные латинские буквы:
#!/bin/sh
for i in $(seq 97 122); do
printf "\\$(printf %o $i)\n"
done
-
seq
Команда генерирует числовые последовательности.$(
)
выполняет подстановку команд, поэтому$(seq 97 122)
заменяется на выводseq 97 122
, Это коды символов дляa
черезz
, - Мощный
printf
команда может превратить коды символов в буквы (например,printf '\141'
печатьa
с последующим переводом строки), но коды должны быть в восьмеричном виде, аseq
выводит только в десятичном формате. Итак, я использовалprintf
дважды: внутреннийprintf %o $i
преобразует десятичные числа (предоставляетсяseq
) в восьмеричное, и подставляется во внешнееprintf
команда. (Хотя также возможно использовать шестнадцатеричное, оно не проще и кажется менее переносимым.) printf
толкует\
за которым следует восьмеричное число в качестве символа с этим кодом, и\n
как новая строка. Но оболочка также использует\
как побег персонажа.\
перед$
помешает$
чтобы не происходило расширение (в данном случае подстановка команд), но я не хочу этого предотвращать, поэтому я избежал его с другим\
; это причина\\
, Второй\
доn
не нужно избегать, потому что, в отличие от\$
,\n
не имеет особого значения для оболочки в строке в двойных кавычках.- Для получения дополнительной информации о том, как двойные кавычки и обратный слеш используются в программировании оболочки, см. Раздел о цитировании в международном стандарте. См. Также 3.1.2 Цитирование в Справочном руководстве Bash, особенно 3.1.2.1 Escape-символ и 3.1.2.3 Двойные кавычки. (Вот весь раздел в контексте.) Обратите внимание, что одинарные кавычки (
'
) также являются важной частью синтаксиса цитирования оболочки, я просто не использовал их в этом сценарии.
Это переносимо для большинства Unix-подобных систем и не зависит от того, какую оболочку в стиле Bourne вы используете. Однако некоторые Unix-подобные системы не имеют seq
установлен по умолчанию (они, как правило, используют jot
вместо этого, который не установлен по умолчанию в большинстве систем GNU/Linux). Вы можете использовать цикл с expr
или арифметическая замена для дальнейшего увеличения переносимости, если вам необходимо:
#!/bin/sh
i=97
while [ $i -le 122 ]; do
printf "\\$(printf %o $i)\n"
i=$((i + 1))
done
Который использует while
петля с [
Команда продолжить цикл только тогда, когда $i
находится в диапазоне.
Вместо того, чтобы печатать весь алфавит, ваш скрипт определяет переменную n
и печатает первый $n
строчные буквы. Вот версия вашего скрипта, которая не использует специфичные для Bash функции и работает на Dash, но требует seq
:
#!/bin/sh
n=3 start=97
for i in $(seq $start $((start + n - 1))); do
printf "\\$(printf %o $i)\n"
done
Регулировка стоимости n
изменяет количество напечатанных букв, как в вашем скрипте.
Вот версия, которая не требует seq
:
#!/bin/sh
n=3 i=97 stop=$((i + n))
while [ $i -lt $stop ]; do
printf "\\$(printf %o $i)\n"
i=$((i + 1))
done
Там, $stop
на единицу выше, чем код символа последней буквы, которая должна быть напечатана, поэтому я использую -lt
(меньше чем) а не -le
(меньше или равно) с [
команда. (Это также помогло бы сделать stop=$((i + n - 1))
и использовать [ $i -le $stop ]
).