Czy w powłoce jest coś takiego jak „split ()” JavaScript?


18

W split()JavaScripcie bardzo łatwo jest podzielić łańcuch na tablicę.

Co ze skryptem powłoki?

Powiedz, że chcę to zrobić:

$ script.sh var1_var2_var3

Gdy użytkownik poda taki ciąg var1_var2_var3do script.sh, wewnątrz skryptu przekształci ciąg w podobną tablicę

array=( var1 var2 var3 )
for name in ${array[@]}; do
    # some code
done

1
czego shellużywasz, z bashczym możesz zrobićIFS='_' read -a array <<< "${string}"
gwillie

perlteż mogę to zrobić. To nie jest „czysta” powłoka, ale jest dość powszechna.
Sobrique,

@Sobrique Nie znam też technicznej definicji „czystej” powłoki, ale istnieje node.js.
emory

Zwykle pracuję nad „czy to prawdopodobnie jest domyślnie zainstalowane na moim Linux
ie

Odpowiedzi:


24

Powłoki typu Bourne / POSIX mają operator split + glob i jest on wywoływany za każdym razem, gdy nie podaje się wyrażenia parametru ( $var, $-...), podstawienia polecenia ( $(...)) lub rozszerzenia arytmetycznego ( $((...))) w kontekście listy.

Właściwie przywołałeś go przez pomyłkę, kiedy to zrobiłeś for name in ${array[@]}zamiast for name in "${array[@]}". (W rzeczywistości powinieneś wystrzegać się tego, że przywołanie takiego operatora przez pomyłkę jest źródłem wielu błędów i luk w zabezpieczeniach ).

Ten operator jest skonfigurowany z $IFSparametrem specjalnym (aby powiedzieć, na jakie znaki mają się dzielić (choć uważaj, że spacja, tabulator i znak nowej linii są tam specjalnie traktowane)) i -fopcją wyłączenia ( set -f) lub włączenia ( set +f) globczęści.

Zauważ też, że chociaż Sin $IFSbył pierwotnie (w powłoce Bourne'a, skąd $IFSpochodzi) dla Separatora, w powłokach POSIX, znaki w $IFSpowinny być raczej postrzegane jako separatory lub terminatory (patrz przykład poniżej).

Podzielmy się na _:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
array=($string) # invoke the split+glob operator

for i in "${array[@]}"; do # loop over the array elements.

Aby zobaczyć różnicę między separatorem a separatorem , spróbuj:

string='var1_var2_'

To będzie podzielić ją na var1i var2tylko (bez dodatkowych pusty element).

Aby więc był podobny do skryptów JavaScript split(), potrzebujesz dodatkowego kroku:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
temp=${string}_ # add an extra delimiter
array=($temp) # invoke the split+glob operator

(zwróć uwagę, że podzieliłby pusty element $stringna 1 (nie 0 ), jak JavaScript split()).

Aby zobaczyć kartę specjalnych zabiegów, spację i znak nowej linii, porównaj:

IFS=' '; string=' var1  var2  '

(skąd bierzesz var1i var2) z

IFS='_'; string='_var1__var2__'

gdzie można uzyskać: '', var1, '', var2, ''.

Zauważ, że zshpowłoka nie wywołuje tego operatora split + glob domyślnie w ten sposób, chyba że w shlub kshemulacji. Tam musisz je bezpośrednio przywołać. $=stringdla części podzielonej, $~stringdla części globalnej ( $=~stringdla obu), a także ma operator podziału, w którym można określić separator:

array=(${(s:_:)string})

lub w celu zachowania pustych elementów:

array=("${(@s:_:)string}")

Należy zauważyć, że nie sjest do rozszczepiania , nie ograniczające (również $IFS, znany POSIX niezgodności z zsh). Różni się od skryptów JavaScript tym, split()że pusty ciąg jest podzielony na element 0 (nie 1).

Godnym Różnica $IFS-splitting jest to, że ${(s:abc:)string}podziały na abcstruny, natomiast z IFS=abc, które podzielone na a, blub c.

Za pomocą zshi ksh93, specjalne traktowanie, które otrzymują spacja, tabulator lub znak nowej linii, można usunąć, podwajając je $IFS.

Historycznie rzecz biorąc, powłoka Bourne'a (przodek lub współczesne pociski POSIX) zawsze usuwała puste elementy. Miał także szereg błędów związanych z dzieleniem i rozszerzaniem $ @ o wartościach innych niż default $IFS. Na przykład IFS=_; set -f; set -- $@nie byłoby równoważne z IFS=_; set -f; set -- $1 $2 $3....

Dzielenie wyrażeń regularnych

Teraz, aby znaleźć coś bliższego JavaScript'owi, split()który może dzielić się na wyrażenia regularne, musisz polegać na zewnętrznych narzędziach.

W skrzynce narzędzi POSIX awkma splitoperator, który może dzielić rozszerzone wyrażenia regularne (są one mniej więcej podzbiorem wyrażeń regularnych podobnych do Perla obsługiwanych przez JavaScript).

split() {
  awk -v q="'" '
    function quote(s) {
      gsub(q, q "\\" q q, s)
      return q s q
    }
    BEGIN {
      n = split(ARGV[1], a, ARGV[2])
      for (i = 1; i <= n; i++) printf " %s", quote(a[i])
      exit
    }' "$@"
}
string=a__b_+c
eval "array=($(split "$string" '[_+]+'))"

zshPowłoka posiada wbudowane wsparcie dla kompatybilnych Perl wyrażeń regularnych (w jego zsh/pcremoduł), ale przy użyciu go podzielić ciąg, choć możliwe jest stosunkowo kłopotliwe.


Czy jest jakiś powód do specjalnych zabiegów z tabulatorem, spacją i znakiem nowej linii?
cuonglm

1
@cuonglm, ogólnie chcesz podzielić się na słowach, gdy ograniczniki są wykroje, w przypadku nie-pustych ograniczniki (jak do rozłamu $PATHna :) Wręcz przeciwnie, na ogół chcą zachować pustych elementów. Zauważ, że w powłoce Bourne'a wszystkie postacie otrzymywały specjalne traktowanie, kshzmieniono tak, aby traktować tylko puste (tylko spację, tabulator i znak nowej linii) specjalnie.
Stéphane Chazelas,

Cóż, ostatnio dodana notatka muszli Bourne'a mnie zaskoczyła. A jeśli chcesz uzupełnić, czy powinieneś dodać notatkę dotyczącą zshleczenia ciągiem zawierającym 2 lub więcej znaków w ${(s:string:)var}? Jeśli
dodam

1
Co rozumiesz przez „Zauważ też, że S w $ IFS jest dla ogranicznika, a nie separatora”. Rozumiem mechanikę i że ignoruje ona końcowe separatory, ale Soznacza Separator , a nie ogranicznik . Przynajmniej tak mówi instrukcja mojej bash.
terdon

@terdon, $IFSpochodzi z powłoki Bourne'a, w której był separatorem , ksh zmienił zachowanie bez zmiany nazwy. Wspominam o tym, aby podkreślić, że split+glob(z wyjątkiem zsh lub pdksh) nie dzieli się już po prostu.
Stéphane Chazelas,

7

Tak, użyj IFSi ustaw na _. Następnie użyj read -ado zapisania w tablicy ( -rwyłącza rozwinięcie odwrotnego ukośnika). Zauważ, że jest to specyficzne dla bash; ksh i zsh mają podobne funkcje z nieco inną składnią, a zwykły sh w ogóle nie ma zmiennych tablicowych.

$ r="var1_var2_var3"
$ IFS='_' read -r -a array <<< "$r"
$ for name in "${array[@]}"; do echo "+ $name"; done
+ var1
+ var2
+ var3

Od man bash:

czytać

- aname

Słowa są przypisywane do kolejnych wskaźników indeksu zmiennej tablicowej, zaczynając od 0. aname jest rozbrojone przed przypisaniem jakichkolwiek nowych wartości. Inne argumenty nazwy są ignorowane.

IFS

Wewnętrzny separator pól używany do dzielenia słów po rozwinięciu i dzielenia wierszy na słowa za pomocą wbudowanego polecenia odczytu. Wartość domyślna to `` ''.

Zauważ, że readzatrzymuje się przy pierwszej nowej linii. Przejdź, -d ''aby readtego uniknąć, ale w takim przypadku na końcu pojawi się dodatkowa nowa linia ze względu na <<<operatora. Możesz go usunąć ręcznie:

IFS='_' read -r -d '' -a array <<< "$r"
array[$((${#array[@]}-1))]=${array[$((${#array[@]}-1))]%?}

Zakłada się, $rże nie zawiera znaków nowej linii ani odwrotnych ukośników. Zauważ też, że będzie działać tylko w najnowszych wersjach bashpowłoki.
Stéphane Chazelas,

@ StéphaneChazelas dobry punkt. Tak, jest to „podstawowy” przypadek łańcucha. Co do reszty, wszyscy powinni skorzystać z kompleksowej odpowiedzi. Jeśli chodzi o wersje bash, read -azostał wprowadzony w bash 4, prawda?
fedorqui

1
przepraszam mój zły, myślałem, że <<<został dodany dopiero niedawno, bashale wygląda na to, że był tam od 2.05b (2002). read -ajest jeszcze starszy. <<<pochodzi z zshi jest obsługiwany przez ksh93(oraz mksh i yash), ale read -ajest specyficzny dla bash (jest -Aw ksh93, yash i zsh).
Stéphane Chazelas,

@ StéphaneChazelas czy jest jakiś „łatwy” sposób, aby dowiedzieć się, kiedy nastąpiły te zmiany? Mówię „łatwe”, aby nie zagłębiać się w pliki wydania, może strona pokazująca je wszystkie.
fedorqui

1
Patrzę na to w dziennikach zmian. Zsh posiada również repozytorium git z historią już w wersji 3.1.5, a jego lista mailingowa służy również do śledzenia zmian.
Stéphane Chazelas,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.