Do pętli z alfabetem

12

Działa to doskonale w OSX

#!/bin/bash
chars=( {a..z} )
n=3
for ((i=0; i<n; i++))
do
  echo "${chars[i]}"
done

Ale kiedy uruchamiam go na Ubuntu, pojawia się następujący błąd.

ForLoopAlphabetTest.sh: 2: ForLoopAlphabetTest.sh: Syntax error: "(" unexpected

Nie mogę rozwiązać problemu. Jakieś sugestie?

denski
źródło
2
Działa to na Ubuntu.
Pilot6
Nie mogę go uruchomić w 16.04 bash 4.3 jako skrypt. Ale działa, jeśli skopiuję go do terminala.
denski

Odpowiedzi:

25

Prawdopodobnie uruchamiasz skrypt jako:

sh ForLoopAlphabetTest.sh

W Ubuntu shjest symbolicznie połączony dash; ponieważ dashnie ma pojęcia o tablicach, pojawia się błąd składniowy dla (.

Skrypt działa idealnie bash, więc byłoby dobrze, gdybyś uruchomił go jako bashargument:

bash ForLoopAlphabetTest.sh

Teraz masz bashskrypt na skrypcie, abyś mógł wykonać skrypt ( chmod u+x ForLoopAlphabetTest.sh) i uruchomić go jako:

/path/to/ForLoopAlphabetTest.sh

lub z katalogu skryptu:

./ForLoopAlphabetTest.sh

Zauważ też, że twój skrypt zawiera rozwinięcie nawiasu {a..z}i forkonstrukcję w stylu C : for (( ... ))które również nie są obsługiwane przez dash; więc jeśli twoim celem jest przenośność, powinieneś spojrzeć tylko na shskładnie POSIX .

heemayl
źródło
Dziękuję Ci. Czy istnieje sposób na obejście braku pojęcia tablic?
denski
3
@denski Jeśli chcesz pisać przenośne skrypty, które mogą być uruchamiane /bin/shw dowolnym systemie operacyjnym podobnym do Uniksa, nie będziesz mógł używać tablic. Bash (i niektóre inne powłoki) dodały je, ponieważ są bardzo wygodne i nie zawsze można je łatwo zastąpić bardziej przenośnym kodem. Jednak w szczególności w przypadku skryptu możesz to zrobić bez problemu i bez korzystania z funkcji specyficznych dla bash. Czy jesteś zainteresowany tym, jak to zrobić?
Eliah Kagan
Jeśli masz jakieś sugerowane lektury, byłoby to pomocne. Dzięki.
denski
1
@denski Zamieściłem odpowiedź, która zawiera kilka linków i przykładów. W moim wcześniejszym komentarzu wspomniałem, że używałeś tablic i stylu C dla pętli, ale nie wspomniałem o używaniu rozszerzenia nawiasów klamrowych. Moja odpowiedź dotyczy tego, jak sobie radzić bez wszystkich trzech. Zauważ, że ta odpowiedź (tj. Heemayl, a nie moja) jest podstawowym rozwiązaniem twojego problemu; mine koncentruje się na tym, jak możesz przepisać skrypt, jeśli nie możesz polegać na funkcjach specyficznych dla bash.
Eliah Kagan
@ heemayl Dla przypomnienia, chciałem dodać, że miałeś rację, zakładając, że uruchamiam skryptysh
denski
10

Twój skrypt wykorzystuje trzy funkcje powłoki Bash, które nie są dostępne we wszystkich powłokach typu Bourne'a. Jak mówi heemayl , możesz po prostu uruchomić ten skrypt bashzamiast sh. Twój hashbang linia u góry ( #!/bin/bash) stanowi, bashale jest skuteczna tylko jeśli wykonanie skryptu, jak heemayl wyjaśnione . Jeśli podasz nazwę skryptu sh, shnie zadzwoni automatycznie bash, ale po prostu uruchomi skrypt. Wynika to z faktu, że po uruchomieniu skryptu linia hashbang nie działa .

Inną alternatywą, jeśli musisz pisać w pełni przenośne skrypty, które nie zależą od funkcji Bash, jest zmiana skryptu, aby działał bez nich. Funkcje Bash, których używasz to:

Bash jest szeroko dostępny, szczególnie na systemach GNU / Linux, takich jak Ubuntu, i (jak widzieliście) jest również dostępny na macOS i wielu innych systemach. Biorąc pod uwagę, jak często korzystasz z funkcji specyficznych dla Bash, możesz po prostu z nich skorzystać i po prostu upewnij się, że używasz Bash (lub innej powłoki obsługującej funkcje, których używasz) podczas uruchamiania skryptów.

Możesz jednak zastąpić je przenośnymi konstrukcjami, jeśli chcesz. Tablica i forpętla w stylu C można łatwo wymienić; generowanie zakresu liter bez rozwijania nawiasów klamrowych (i bez kodowania ich na stałe w skrypcie) to część, która jest nieco trudna.


Po pierwsze, oto skrypt, który drukuje wszystkie małe litery łacińskie:

#!/bin/sh

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

Jest to przenośne dla większości systemów uniksowych i nie zależy od używanej powłoki w stylu Bourne'a. Jednak kilka systemów uniksowych nie jest seqinstalowanych domyślnie (zwykle używają ich jot, co nie jest instalowane domyślnie w większości systemów GNU / Linux). Możesz użyć pętli z exprpodstawieniem arytmetycznym lub podstawienia arytmetycznego, aby jeszcze bardziej zwiększyć przenośność, jeśli chcesz:

#!/bin/sh

i=97
while [ $i -le 122 ]; do
    printf "\\$(printf %o $i)\n"
    i=$((i + 1))
done

Który wykorzystuje while-loop z tym [poleceniem , aby kontynuować pętle tylko gdy $ijest w zasięgu.


Zamiast drukować cały alfabet, skrypt definiuje zmienną ni drukuje pierwsze $nmałe litery. Oto wersja skryptu, która nie korzysta z funkcji specyficznych dla Bash i działa w Dash, ale wymaga seq:

#!/bin/sh

n=3 start=97
for i in $(seq $start $((start + n - 1))); do
    printf "\\$(printf %o $i)\n"
done

Dostosowywanie wartości nzmian o liczbę drukowanych liter, tak jak w skrypcie.

Oto wersja, która nie wymaga seq:

#!/bin/sh

n=3 i=97 stop=$((i + n))
while [ $i -lt $stop ]; do
    printf "\\$(printf %o $i)\n"
    i=$((i + 1))
done

Jest o $stopjeden wyższy niż kod znakowy ostatniej litery, który powinien zostać wydrukowany, więc używam -lt(mniej niż) niż -le(mniej niż lub równo) z [poleceniem. (Byłoby również działać, aby zrobić stop=$((i + n - 1))i używać [ $i -le $stop ]).

Eliah Kagan
źródło
1
To jest fenomenalnie szczegółowa odpowiedź, niż w przypadku edukacji. Jestem bardzo początkującym, więc mój sposób pisania skryptów łączy ze sobą działające elementy znalezione w Internecie, dopóki nie zadziała. Nie mam wątpliwości, że są 1) lepsze i 2) prostsze sposoby robienia rzeczy, które tworzę za pomocą skryptów, a powyższe informacje znacznie pomagają w tym.
denski
Powiązane: stackoverflow.com/questions/169511/...
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件