Для цикла с алфавитом

12

Это отлично работает на 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

Я не могу решить эту проблему. Какие-либо предложения?

denski
источник
2
Это работает на Ubuntu.
Pilot6
Я не могу заставить его работать на 16.04 bash 4.3 как скрипт. Но это работает, если я скопирую его в терминал.
Денски

Ответы:

25

Предположительно, вы запускаете скрипт как:

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}и forконструкцию в стиле C : for (( ... ))которые также не поддерживаются dash; поэтому, если ваша цель - переносимость, вы должны смотреть только на shсинтаксис POSIX .

heemayl
источник
Спасибо. Есть ли способ обойти черту отсутствия концепции массива?
Денски
3
@denski Если вы хотите писать переносимые сценарии, которые могут выполняться /bin/shв любой Unix-подобной операционной системе, вы не сможете использовать массивы. Bash (и некоторые другие оболочки) добавили их, потому что они очень удобны и не всегда могут быть легко заменены более переносимым кодом. Тем не менее, для вашего скрипта, в частности, вы можете сделать это без проблем и без использования каких-либо специфичных для bash функций. Вы заинтересованы в том, как это сделать?
Элия ​​Каган
Если у вас есть некоторые предложения по чтению, это было бы полезно. Благодарю.
Денски
1
@denski Я разместил ответ, который включает в себя некоторые ссылки и примеры. В моем предыдущем комментарии здесь я упоминал, что вы использовали массивы и стиль C для циклов, но не упомянул о вашем использовании расширения скобок. Мой ответ охватывает, как обойтись без всех трех. Обратите внимание, что этот ответ (т.е. мой, а не мой) является основным решением вашей проблемы; Мой рассказ посвящен тому, как вы можете переписать свой скрипт, если не можете полагаться на специфичные для bash функции.
Элия ​​Каган
@heemayl Для справки, я хотел бы добавить, что вы были правы в своем предположении, что я выполняю сценарии сsh
denski
10

В вашем сценарии используются три функции оболочки Bash, которые предоставляются не всеми оболочками в стиле Борна. Как говорит Химэйл , вы можете просто запустить этот скрипт bashвместо sh. Ваша строка hashbang вверху ( #!/bin/bash) указывает, bashно эффективна только если вы выполняете скрипт, как объяснил heemayl . Если вы передадите имя скрипта sh, shон не будет автоматически вызываться bash, а просто запустит скрипт. Это потому, что когда ваш скрипт действительно запущен, строка hashbang не имеет никакого эффекта .

Другой вариант, если вам нужно написать полностью переносимые скрипты, которые не зависят от функций Bash, - это изменить ваш скрипт так, чтобы он работал без них. Используемые вами функции Bash:

Bash широко доступен, особенно в системах GNU / Linux, таких как Ubuntu, и (как вы видели) также доступен в macOS и многих других системах. Учитывая, сколько вы используете специфичных для Bash функций, вы можете просто захотеть использовать их и просто убедиться, что вы используете Bash (или какую-либо другую оболочку, которая поддерживает используемые вами функции) при запуске ваших сценариев.

Однако вы можете заменить их переносимыми конструкциями, если хотите. Массив и forцикл в стиле C легко заменить; генерация диапазона букв без расширения фигурных скобок (и без жесткого кодирования их в вашем скрипте) - это немного сложная часть.


Во-первых, вот скрипт, который печатает все строчные латинские буквы:

#!/bin/sh

for i in $(seq 97 122); do
    printf "\\$(printf %o $i)\n"
done

Это переносимо для большинства 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-loop с в [команде , чтобы продолжить цикл только тогда , когда $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 ]).

Элия ​​Каган
источник
1
Это феноменально подробный ответ, чем вы по образованию. Я очень начинающий, поэтому мой способ написания сценариев сводит воедино рабочие элементы, найденные в Интернете, до тех пор, пока они не будут работать. Я не сомневаюсь, что есть 1) лучшие и 2) более простые способы делать то, что я создаю с помощью скриптов, и вышеописанное помогает мне в этом.
Денски
Связанный: stackoverflow.com/questions/169511/…
Сиро Сантилли 新疆 改造 中心 法轮功 六四 事件