W skrypcie Bash chciałbym podzielić linię na części i przechowywać je w tablicy.
Linia:
Paris, France, Europe
Chciałbym mieć je w tablicy takiej jak ta:
array[0] = Paris
array[1] = France
array[2] = Europe
Chciałbym użyć prostego kodu, szybkość polecenia nie ma znaczenia. Jak mogę to zrobić?
,
(przecinek-spacja), a nie o pojedynczy znak, taki jak przecinek. Jeśli interesuje Cię tylko ta ostatnia, odpowiedzi tutaj są łatwiejsze do naśladowania: stackoverflow.com/questions/918886/...cut
warto o tym pamiętać. Separator jest definiowalny en.wikibooks.org/wiki/Cut Można również wyodrębnić dane ze struktury rekordów o stałej szerokości. en.wikipedia.org/wiki/Cut_(Unix) computerhope.com/unix/ucut.htmOdpowiedzi:
Należy zauważyć, że znaki
$IFS
są traktowane indywidualnie jako separatory tak, że w tym przypadku, pola mogą być oddzielone albo przecinkiem lub przestrzeni, a nie z dwóch sekwencji znaków. Co ciekawe, puste pola nie są tworzone, gdy na wejściu pojawia się przecinek, ponieważ spacja jest traktowana specjalnie.Aby uzyskać dostęp do pojedynczego elementu:
Aby iterować po elementach:
Aby uzyskać zarówno indeks, jak i wartość:
Ostatni przykład jest przydatny, ponieważ tablice Bash są rzadkie. Innymi słowy, możesz usunąć element lub dodać element, a wtedy indeksy nie są ciągłe.
Aby uzyskać liczbę elementów w tablicy:
Jak wspomniano powyżej, tablice mogą być rzadkie, więc nie należy używać długości, aby uzyskać ostatni element. Oto, jak możesz to zrobić w Bash 4.2 i nowszych wersjach:
w dowolnej wersji Basha (skądś po wersji 2.05b):
Większe ujemne przesunięcia należy wybierać dalej od końca tablicy. Zwróć uwagę na spację przed znakiem minus w starszej formie. Jest wymagane.
źródło
IFS=', '
, wtedy nie musisz usuwać spacji osobno. Test:IFS=', ' read -a array <<< "Paris, France, Europe"; echo "${array[@]}"
declare -p array
wyników testowych.France, Europe, "Congo, The Democratic Republic of the"
podzieli się to po Kongu.str="Paris, France, Europe, Los Angeles"; IFS=', ' read -r -a array <<< "$str"
podzieli sięarray=([0]="Paris" [1]="France" [2]="Europe" [3]="Los" [4]="Angeles")
na notatkę. Działa to zatem tylko z polami bez spacji, ponieważIFS=', '
jest to zestaw pojedynczych znaków - nie ogranicznik łańcucha.Wszystkie odpowiedzi na to pytanie są błędne w taki czy inny sposób.
Zła odpowiedź nr 1
1: To niewłaściwe użycie
$IFS
. Wartość$IFS
zmiennej nie jest traktowana jako pojedynczy separator ciągów o zmiennej długości , a raczej jako zestaw separatorów ciągów o jednym znaku , w którym każde poleread
oddzielające się od linii wejściowej może być zakończone dowolnym znakiem w zestawie (przecinek lub spacja, w tym przykładzie).W rzeczywistości, dla prawdziwych kijów tam, pełne znaczenie
$IFS
jest nieco bardziej zaangażowane. Z podręcznika bash :Zasadniczo w przypadku wartości domyślnych niepustych wartości
$IFS
pola można oddzielić (1) sekwencją jednego lub więcej znaków, które wszystkie pochodzą ze zbioru „znaków białych znaków IFS” (to znaczy, którekolwiek z <spacji> , <tab> i <newline> („nowa linia”, co oznacza przesunięcie wiersza (LF) ) są obecne w dowolnym miejscu$IFS
) lub (2) dowolny inny niż „znak białych znaków IFS”, który jest obecny$IFS
wraz z otaczającymi go „znakami białych znaków IFS” w linii wejściowej.W przypadku OP możliwe jest, że drugi tryb separacji, który opisałem w poprzednim akapicie, jest dokładnie tym, czego chce dla swojego ciągu wejściowego, ale możemy być całkiem pewni, że pierwszy tryb separacji, który opisałem, nie jest wcale poprawny. Na przykład, co jeśli jego ciąg wejściowy był
'Los Angeles, United States, North America'
?2: Nawet jeśli użyjesz tego rozwiązania z separatorem jednoznakowym (takim jak przecinek sam w sobie, to znaczy bez następnej spacji lub innego bagażu), jeśli wartość
$string
zmiennej zawiera jakieś LF, toread
będzie zatrzymać przetwarzanie, gdy napotka pierwszy LF.read
Wbudowane przetwarza tylko jedną linię za wezwaniem. Jest to prawdą, nawet jeśli przesyłasz dane wejściowe lub przekierowujesz tylko doread
instrukcji, tak jak robimy w tym przykładzie z mechanizmem łańcuchowym , a zatem nieprzetworzone dane wejściowe są gwarantowane, że zostaną utracone. Kod, który zasilaread
wbudowane narzędzie, nie ma wiedzy o przepływie danych w ramach zawierającej go struktury poleceń.Można argumentować, że raczej nie spowoduje to problemu, ale jest to subtelne zagrożenie, którego należy unikać, jeśli to możliwe. Jest to spowodowane faktem, że
read
wbudowane narzędzie faktycznie dzieli dwa poziomy podziału danych wejściowych: najpierw na linie, a następnie na pola. Ponieważ OP chce tylko jednego poziomu podziału, to użycieread
wbudowanej funkcji nie jest właściwe i należy tego unikać.3: Nieoczywistym potencjalnym problemem związanym z tym rozwiązaniem jest to, że
read
zawsze upuszcza końcowe pole, jeśli jest puste, chociaż w przeciwnym razie zachowuje puste pola. Oto demo:Może OP nie przejmowałby się tym, ale nadal jest to ograniczenie, o którym warto wiedzieć. Zmniejsza solidność i ogólność rozwiązania.
Ten problem można rozwiązać, dodając atrapę ogranicznika końcowego do ciągu wejściowego tuż przed jego podaniem
read
, co pokażę później.Zła odpowiedź # 2
Podobny pomysł:
(Uwaga: dodałem brakujące nawiasy wokół podstawiania poleceń, które, jak się zdaje, zostało pominięte przez odpowiadającego).
Podobny pomysł:
Rozwiązania te wykorzystują dzielenie słów w przypisaniu tablicowym, aby podzielić ciąg na pola. Co zabawne, podobnie jak
read
w ogólnym dzieleniu słów również używana jest$IFS
specjalna zmienna, chociaż w tym przypadku sugeruje się, że jest ustawiona domyślna wartość <space><tab> <newline> , a zatem dowolna sekwencja jednego lub więcej IFS znaki (które teraz są teraz białymi znakami) są uznawane za separatory pól.Rozwiązuje to problem popełnienia dwóch poziomów podziału
read
, ponieważ samo dzielenie słów stanowi tylko jeden poziom podziału. Ale tak jak poprzednio, problem polega na tym, że poszczególne pola w ciągu wejściowym mogą już zawierać$IFS
znaki, a zatem byłyby niepoprawnie podzielone podczas operacji dzielenia słów. Zdarza się, że nie dzieje się tak w przypadku żadnego z przykładowych ciągów wejściowych dostarczonych przez tych użytkowników (jak wygodne ...), ale oczywiście nie zmienia to faktu, że każda podstawa kodu, która użyłaby tego idiomu, naraziłaby na ryzyko wysadzenie w powietrze, jeśli to założenie zostanie kiedykolwiek naruszone w pewnym momencie. Jeszcze raz rozważ mój kontrprzykład'Los Angeles, United States, North America'
(lub'Los Angeles:United States:North America'
).Również podział na słowa jest zwykle następnie rozszerzenia nazwy pliku ( vel rozszerzalności ścieżce, aka masek), który jeśli odbywa się, że potencjalnie uszkodzone słowa zawierające znaki
*
,?
lub[
następnie]
(a jeśliextglob
jest ustawiony w nawiasach fragmenty poprzedzony?
,*
,+
,@
, lub!
) poprzez dopasowanie ich do obiektów systemu plików i odpowiednie rozwinięcie słów („globs”). Pierwszy z tych trzech odpowiadających sprytnie podciął ten problem, uruchamiającset -f
wcześniej, aby wyłączyć globowanie. Technicznie działa to (choć prawdopodobnie należy dodaćset +f
później do ponownego włączenia globowania dla kolejnego kodu, który może od niego zależeć), ale niepożądane jest wprowadzanie bałaganu w globalnych ustawieniach powłoki w celu zhakowania podstawowej operacji parsowania łańcucha na tablicę w kodzie lokalnym.Innym problemem związanym z tą odpowiedzią jest to, że wszystkie puste pola zostaną utracone. Może to stanowić problem, w zależności od aplikacji.
Uwaga: jeśli zamierzasz skorzystać z tego rozwiązania, lepiej użyć
${string//:/ }
formy rozszerzania parametrów w formie „podstawienia wzorca” , zamiast kłopotać się z wywołaniem podstawienia polecenia (które powoduje rozwarcie powłoki), uruchomieniem potoku i uruchamianie zewnętrznego pliku wykonywalnego (tr
lubsed
), ponieważ interpretacja parametrów jest wyłącznie operacją wewnętrzną powłoki. (Ponadto w przypadku rozwiązańtr
ised
zmienna wejściowa powinna być podwójnie cytowana w podstawieniu polecenia; w przeciwnym razie podział słowa zadziałałby wecho
poleceniu i potencjalnie bałaganiłby wartości pól. Również$(...)
forma podstawienia polecenia jest lepsza niż stara`...`
formularza, ponieważ upraszcza zagnieżdżanie podstawień poleceń i pozwala na lepsze wyróżnianie składni przez edytory tekstu).Zła odpowiedź # 3
Ta odpowiedź jest prawie taka sama jak # 2 . Różnica polega na tym, że odpowiadający przyjął założenie, że pola są rozdzielone dwoma znakami, z których jeden jest reprezentowany domyślnie
$IFS
, a drugi nie. Rozwiązał ten dość szczególny przypadek, usuwając znak nie reprezentowany przez IFS, używając rozszerzenia podstawiania wzorca, a następnie stosując dzielenie słów, aby podzielić pola na pozostałym znaku ograniczającym reprezentowanym przez IFS.To nie jest bardzo ogólne rozwiązanie. Co więcej, można argumentować, że przecinek jest tak naprawdę „głównym” znakiem ograniczającym tutaj i że usuwanie go, a następnie zależnie od znaku spacji do dzielenia pól jest po prostu niewłaściwe. Po raz kolejny, że mój kontrprzykład:
'Los Angeles, United States, North America'
.Również ponownie rozszerzenie nazwy pliku może uszkodzić rozwinięte słowa, ale można temu zapobiec, tymczasowo wyłączając globowanie dla przypisania za pomocą,
set -f
a następnieset +f
.Ponownie wszystkie puste pola zostaną utracone, co może, ale nie musi stanowić problemu, w zależności od aplikacji.
Zła odpowiedź # 4
Jest to podobne do # 2 i # 3 , ponieważ wykorzystuje dzielenie słów, aby wykonać zadanie, tylko teraz kod jawnie ustawia się tak,
$IFS
aby zawierał tylko jednoznakowy separator pola obecny w ciągu wejściowym. Należy powtórzyć, że nie może to działać w przypadku ograniczników wieloznakowych, takich jak ogranicznik przecinka w PO. Ale w przypadku ogranicznika jednoznakowego, takiego jak LF zastosowanego w tym przykładzie, w rzeczywistości jest on prawie idealny. Pola nie mogą zostać przypadkowo podzielone na środku, jak widzieliśmy przy poprzednich błędnych odpowiedziach, i istnieje tylko jeden poziom podziału, zgodnie z wymaganiami.Jednym z problemów jest to, że rozszerzenie nazwy pliku uszkodzi słowa, których to dotyczy, jak opisano wcześniej, chociaż można to rozwiązać, umieszczając krytyczne zdanie w
set -f
iset +f
.Innym potencjalnym problemem jest to, że ponieważ LF kwalifikuje się jako „znak białych znaków IFS”, jak zdefiniowano wcześniej, wszystkie puste pola zostaną utracone, tak jak w punktach 2 i 3 . Oczywiście nie stanowiłoby to problemu, gdyby separator był innym niż „znakiem białych znaków IFS”, a w zależności od aplikacji i tak może nie mieć znaczenia, ale narusza ogólność rozwiązania.
Tak więc, podsumowując, zakładając, że masz jeden ogranicznik-znakowy, i jest to albo nie- „IFS biały znak” lub nie dbają o pustych pól, i owinąć krytyczne oświadczenie
set -f
iset +f
, to rozwiązanie działa , ale poza tym nie.(Również ze względów informacyjnych przypisanie LF do zmiennej w bash można łatwiej wykonać za pomocą
$'...'
składni, npIFS=$'\n';
.)Zła odpowiedź # 5
Podobny pomysł:
To rozwiązanie jest w rzeczywistości skrzyżowaniem między nr 1 (w tym, że ustawia się
$IFS
na przecinek) i # 2-4 (w tym, że wykorzystuje dzielenie słów, aby podzielić ciąg na pola). Z tego powodu cierpi na większość problemów, które dotyczą wszystkich powyższych błędnych odpowiedzi, podobnie jak najgorszy ze wszystkich światów.Również w odniesieniu do drugiego wariantu może się wydawać, że
eval
wywołanie jest całkowicie niepotrzebne, ponieważ jego argument jest dosłownym ciągiem cudzysłowu i dlatego jest statycznie znany. Ale w rzeczywistości korzystanie zeval
tego sposobu jest bardzo nieoczywiste . Zwykle po uruchomieniu prostego polecenia, które składa się tylko z przypisania zmiennej , co oznacza, że nie następuje po nim rzeczywiste słowo polecenia, przypisanie działa w środowisku powłoki:Jest to prawdą, nawet jeśli proste polecenie obejmuje wiele przypisań zmiennych; ponownie, dopóki nie ma słowa polecenia, wszystkie przypisania zmiennych wpływają na środowisko powłoki:
Ale jeśli przypisanie zmiennej jest dołączone do nazwy polecenia (chciałbym to nazwać „przypisaniem prefiksu”), to nie wpływa to na środowisko powłoki, a jedynie wpływa na środowisko wykonanego polecenia, niezależnie od tego, czy jest ono wbudowane lub zewnętrzny:
Odpowiedni cytat z podręcznika bash :
Możliwe jest wykorzystanie tej funkcji przypisywania zmiennych do zmiany
$IFS
tylko tymczasowo, co pozwala nam uniknąć całego zapisu i przywracania gambitu takiego jak ten, który jest wykonywany ze$OIFS
zmienną w pierwszym wariancie. Ale wyzwanie, przed którym stoimy, polega na tym, że polecenie, które musimy wykonać, samo w sobie jest jedynie zmiennym przypisaniem, a zatem nie wymagałoby słowa polecenia, aby uczynić$IFS
przypisanie tymczasowym. Możesz pomyśleć, dlaczego nie po prostu dodać do polecenia słowa zakazu,: builtin
aby$IFS
tymczasowo przypisać to zadanie? To nie działa, ponieważ spowodowałoby to również$array
tymczasowe przypisanie:Tak więc, jesteśmy skutecznie w impasie, trochę w pułapce 22. Ale kiedy
eval
uruchamia swój kod, uruchamia go w środowisku powłoki, tak jakby to był normalny, statyczny kod źródłowy, i dlatego możemy uruchomić$array
przypisanie weval
argumencie, aby zadziałało w środowisku powłoki, podczas gdy$IFS
przypisanie prefiksu to jest poprzedzonyeval
poleceniem, nie przeżyjeeval
polecenia. To jest właśnie sztuczka stosowana w drugim wariancie tego rozwiązania:Tak więc, jak widać, jest to naprawdę sprytna sztuczka i realizuje dokładnie to, co jest wymagane (przynajmniej w odniesieniu do realizacji przypisania) w dość nieoczywisty sposób. W rzeczywistości nie jestem przeciwny tej sztuczce, pomimo zaangażowania
eval
; po prostu ostrożnie, aby zacytować ciąg argumentu, aby uchronić się przed zagrożeniami bezpieczeństwa.Ale znowu, z powodu aglomeracji problemów „najgorszego ze wszystkich światów”, nadal jest to zła odpowiedź na wymagania PO.
Zła odpowiedź # 6
Co? OP ma zmienną łańcuchową, którą należy przeanalizować w tablicy. Ta „odpowiedź” zaczyna się od pełnej treści ciągu wejściowego wklejonego do literału tablicowego. Myślę, że to jeden ze sposobów, aby to zrobić.
Wygląda na to, że odpowiadający mógł założyć, że
$IFS
zmienna wpływa na wszystkie analizy bash we wszystkich kontekstach, co nie jest prawdą. Z podręcznika bash:Tak więc
$IFS
specjalna zmienna jest faktycznie używana tylko w dwóch kontekstach: (1) dzielenie słów, które jest wykonywane po rozwinięciu (to znaczy nie podczas analizowania kodu źródłowego bash) i (2) do dzielenia linii wejściowych na słowa przezread
wbudowane.Pozwól mi spróbować to wyjaśnić. Myślę, że dobrze byłoby rozróżnić parsowanie i wykonanie . Bash musi najpierw przeanalizować kod źródłowy, co oczywiście jest zdarzeniem analizującym , a następnie wykonuje kod, czyli wtedy, gdy pojawia się rozszerzenie. Rozszerzenie jest naprawdę zdarzeniem wykonawczym . Ponadto mam problem z opisem
$IFS
zmiennej, którą właśnie cytowałem powyżej; zamiast powiedzieć, że dzielenie słów odbywa się po rozwinięciu , powiedziałbym, że dzielenie słów odbywa się podczas rozwijania, a może nawet bardziej precyzyjnie, dzielenie słów jest częściąproces ekspansji. Wyrażenie „dzielenie słów” odnosi się tylko do tego etapu ekspansji; nigdy nie należy go używać do odwoływania się do parsowania kodu źródłowego bash, chociaż niestety dokumenty wydają się często rzucać na słowa „split” i „words”. Oto odpowiedni fragment linux.die.net wersji podręcznika bash:Można argumentować, że wersja podręcznika GNU jest nieco lepsza, ponieważ w pierwszym zdaniu rozdziału o rozszerzeniu wybiera słowo „tokeny” zamiast „słowa”:
Ważne jest to,
$IFS
że nie zmienia sposobu, w jaki bash analizuje kod źródłowy. Analiza kodu źródłowego bash jest w rzeczywistości bardzo złożonym procesem, który obejmuje rozpoznawanie różnych elementów gramatyki powłoki, takich jak sekwencje poleceń, listy poleceń, potoki, rozszerzenia parametrów, podstawienia arytmetyczne i podstawienia poleceń. W większości przypadków proces analizowania bash nie może zostać zmieniony przez działania na poziomie użytkownika, takie jak przypisania zmiennych (w rzeczywistości istnieją pewne niewielkie wyjątki od tej reguły; na przykład zobacz różnecompatxx
ustawienia powłoki, które mogą zmienić niektóre aspekty przetwarzania podczas pracy). Początkowe „słowa” / „tokeny”, które wynikają z tego złożonego procesu analizy, są następnie rozwijane zgodnie z ogólnym procesem „ekspansji”, w podziale na powyższe fragmenty dokumentacji, w których dzielenie słów rozwiniętego (rozwijanego?) Tekstu na dalszy słowa to po prostu jeden z kroków tego procesu. Dzielenie wyrazów dotyczy tylko tekstu wypluwanego z poprzedniego kroku rozwijania; nie wpływa na dosłowny tekst, który został przeanalizowany bezpośrednio ze źródłowego strumienia bocznego.Zła odpowiedź # 7
To jedno z najlepszych rozwiązań. Zauważ, że wróciliśmy do używania
read
. Czy nie mówiłem wcześniej, żeread
jest to niewłaściwe, ponieważ wykonuje dwa poziomy podziału, gdy tylko potrzebujemy jednego? Sztuczka polega na tym, że możesz wywoływaćread
w taki sposób, że skutecznie wykonuje tylko jeden poziom podziału, w szczególności poprzez rozdzielenie tylko jednego pola na wywołanie, co wymaga kosztu konieczności wielokrotnego wywoływania go w pętli. To trochę sztuczka, ale działa.Ale są problemy. Po pierwsze: podanie co najmniej jednego argumentu NAZWA
read
powoduje automatyczne ignorowanie początkowych i końcowych białych znaków w każdym polu oddzielonym od ciągu wejściowego. Dzieje się tak niezależnie od tego, czy$IFS
jest ustawiona na wartość domyślną, czy nie, jak opisano wcześniej w tym poście. Teraz OP może nie przejmować się tym ze względu na swój konkretny przypadek użycia, aw rzeczywistości może być pożądaną cechą zachowania podczas analizowania. Ale nie każdy, kto chce parsować ciąg znaków w pola, będzie tego chciał. Istnieje jednak rozwiązanie: nieco nieoczywistym zastosowaniemread
jest przekazywanie zerowych argumentów NAME . W takim przypadkuread
zapisze całą linię wejściową otrzymaną ze strumienia wejściowego w zmiennej o nazwie$REPLY
i, jako bonus, nie będzieusuń początkowe i końcowe białe spacje z wartości. Jest to bardzo solidne użycie, zread
którego często korzystałem w swojej karierze programistycznej. Oto demonstracja różnicy w zachowaniu:Drugi problem z tym rozwiązaniem polega na tym, że tak naprawdę nie zajmuje się przypadkiem niestandardowego separatora pól, takiego jak przecinek PO. Tak jak poprzednio, separatory wieloznakowe nie są obsługiwane, co jest niefortunnym ograniczeniem tego rozwiązania. Możemy spróbować przynajmniej podzielić przecinek, określając separator dla
-d
opcji, ale spójrz, co się stanie:Jak można się było spodziewać, nieznane otaczające białe spacje zostały wciągnięte do wartości pola, a zatem należałoby to później skorygować poprzez operacje przycinania (można to również zrobić bezpośrednio w pętli while). Ale jest jeszcze jeden oczywisty błąd: brakuje Europy! Co się z tym stało? Odpowiedź jest taka, że
read
zwraca nieudany kod powrotu, jeśli trafi na koniec pliku (w tym przypadku możemy go nazwać końcem ciągu) bez napotkania końcowego terminatora pola na polu końcowym. Powoduje to przedwczesne zerwanie pętli while i tracimy końcowe pole.Technicznie ten sam błąd dotyczył również poprzednich przykładów; różnica polega na tym, że jako separator pól przyjęto LF, co jest wartością domyślną, gdy nie podasz
-d
opcji, a mechanizm<<<
(„ciąg tutaj”) automatycznie dołącza LF do łańcucha tuż przed przekazaniem go jako wejście do polecenia. Dlatego w takich przypadkach przypadkowo rozwiązaliśmy problem upuszczenia pola końcowego, nieświadomie dołączając dodatkowy wejściowy terminator do wejścia. Nazwijmy to rozwiązanie „obojętnym terminatorem”. Możemy ręcznie zastosować rozwiązanie fikcyjne-terminator do dowolnego niestandardowego separatora, sami łącząc go z ciągiem wejściowym podczas tworzenia go w ciągu tutaj:Tam problem rozwiązany. Innym rozwiązaniem jest przerwanie pętli while tylko wtedy, gdy oba (1)
read
zwróciły błąd, a (2)$REPLY
jest pusty, co oznacza, żeread
nie był w stanie odczytać żadnych znaków przed trafieniem na koniec pliku. Próbny:Podejście to ujawnia również tajny LF, który jest automatycznie dołączany do ciągu przez
<<<
operator przekierowania. Można go oczywiście usunąć osobno poprzez jawną operację przycinania, jak opisano przed chwilą, ale oczywiście ręczne podejście manekina-terminatora rozwiązuje to bezpośrednio, więc możemy po prostu to zrobić. Ręczne rozwiązanie manekina-terminatora jest w rzeczywistości całkiem wygodne, ponieważ rozwiązuje oba te dwa problemy (problem upuszczonego pola końcowego i dołączony problem LF) za jednym razem.Ogólnie rzecz biorąc, jest to dość potężne rozwiązanie. Jedyną słabością pozostaje brak wsparcia dla ograniczników wieloznakowych, którym zajmę się później.
Zła odpowiedź # 8
(Tak naprawdę pochodzi z tego samego posta co nr 7 ; odpowiadający podał dwa rozwiązania w tym samym poście).
readarray
Wbudowane, które jest synonimemmapfile
, jest idealny. Jest to wbudowane polecenie, które analizuje strumień bajtów w zmienną tablicową w jednym ujęciu; bez bałaganu z pętlami, warunkami, zamianami lub czymkolwiek innym. I nie skrycie ukrywa żadnych białych znaków w ciągu wejściowym. I (jeśli-O
nie podano) wygodnie usuwa tablicę docelową przed przypisaniem do niej. Ale wciąż nie jest idealny, stąd moja krytyka jako „złej odpowiedzi”.Po pierwsze, aby to usunąć, zauważ, że podobnie jak zachowanie
read
podczas parsowania pola,readarray
upuszcza końcowe pole, jeśli jest puste. Ponownie, prawdopodobnie nie dotyczy to PO, ale może dotyczyć niektórych przypadków użycia. Wrócę do tego za chwilę.Po drugie, jak poprzednio, nie obsługuje ograniczników wieloznakowych. Zaraz to naprawię.
Po trzecie, zapisane rozwiązanie nie analizuje ciągu wejściowego OP, a w rzeczywistości nie można go użyć jako takiego do przeanalizowania. Zaraz też to rozwinę.
Z powyższych powodów nadal uważam to za „złą odpowiedź” na pytanie PO. Poniżej podam odpowiedź, którą uważam za właściwą.
Poprawna odpowiedź
Oto naiwna próba sprawienia, by numer 8 działał, po prostu określając
-d
opcję:Widzimy, że wynik jest identyczny z wynikiem uzyskanym z podwójnego warunkowego podejścia
read
rozwiązania zapętlającego omówionego w punkcie 7 . Prawie możemy to rozwiązać za pomocą sztuczki manekina-terminatora:Problem polega na tym, że
readarray
zachowano końcowe pole, ponieważ<<<
operator przekierowania dołączył LF do ciągu wejściowego, a zatem końcowe pole nie było puste (w przeciwnym razie zostałoby upuszczone). Możemy sobie z tym poradzić poprzez jawne rozbrojenie końcowego elementu tablicy po fakcie:Jedynymi dwoma pozostałymi problemami, które są rzeczywiście powiązane, są (1) obce białe znaki, które należy usunąć, oraz (2) brak wsparcia dla ograniczników wieloznakowych.
Oczywiście można później przyciąć biały znak (na przykład zobacz Jak przyciąć biały znak ze zmiennej Bash? ). Ale jeśli uda nam się zhakować ogranicznik wieloznakowy, rozwiąże to oba problemy za jednym razem.
Niestety nie ma bezpośredniego sposobu na uruchomienie ogranicznika wieloznakowego. Najlepszym rozwiązaniem, jakie wymyśliłem, jest wstępne przetworzenie ciągu wejściowego w celu zastąpienia separatora wieloznakowego separatorem jednoznakowym, który gwarantuje, że nie koliduje z zawartością ciągu wejściowego. Jedyny znak, który ma tę gwarancję, to bajt NUL . Wynika to z faktu, że w bash (nawiasem mówiąc nie w Zsh) zmienne nie mogą zawierać bajtu NUL. Ten etap przetwarzania wstępnego można wykonać bezpośrednio w procesie podstawiania. Oto jak to zrobić za pomocą awk :
Tam w końcu! To rozwiązanie nie będzie błędnie dzielić pól na środku, nie przedwcześnie wycinać, nie upuszczać pustych pól, nie uszkadzać się przy rozszerzeniach nazw plików, nie będzie automatycznie usuwać początkowych i końcowych białych znaków, nie pozostawi zagubionego LF na końcu, nie wymaga pętli i nie zadowala się ogranicznikiem jednoznakowym.
Rozwiązanie do przycinania
Na koniec chciałem zademonstrować własne dość skomplikowane rozwiązanie do przycinania przy użyciu niejasnej
-C callback
opcjireadarray
. Niestety skończyło mi się miejsce na drakońskim limicie 30 000 znaków Stack Overflow, więc nie będę w stanie tego wyjaśnić. Zostawię to jako ćwiczenie dla czytelnika.źródło
-d
opcjareadarray
pierwszego pojawienia się w Bash 4.4.awk '{ gsub(/,[ ]+|$/,"\0"); print }'
i wyeliminujesz konkatenację finału", "
, nie musisz przechodzić przez gimnastykę po wyeliminowaniu ostatecznego rekordu. Więc:readarray -td '' a < <(awk '{ gsub(/,[ ]+/,"\0"); print; }' <<<"$string")
na Bash, który obsługujereadarray
. Zauważ, że twoja metoda to Bash 4.4+. Myślę, że z powodu-d
inreadarray
readarray
. W takim przypadku możesz użyć wbudowanego drugiego najlepszego rozwiązaniaread
. Mam na myśli to:a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,";
(zawk
podstawieniem, jeśli potrzebujesz obsługi ogranicznika wieloznakowego). Daj mi znać, jeśli napotkasz jakieś problemy; Jestem prawie pewien, że to rozwiązanie powinno działać na dość starych wersjach basha, powracając do wersji 2-coś, wydanej jak dwie dekady temu.Oto sposób bez ustawiania IFS:
Pomysł polega na zamianie łańcucha:
aby zastąpić wszystkie dopasowania podłańcucha $ spacją, a następnie za pomocą podstawionego łańcucha zainicjować tablicę:
Uwaga: ta odpowiedź korzysta z operatora split + glob . Dlatego, aby zapobiec ekspansji niektórych znaków (np.
*
), Dobrym pomysłem jest wstrzymanie globowania dla tego skryptu.źródło
${string//:/ }
zapobiega rozszerzeniu powłokiarray=(${string//:/ })
Drukuje trzy
źródło
a=($(echo $t | tr ',' "\n"))
. Ten sam wynik za=($(echo $t | tr ',' ' '))
.VERSION="16.04.2 LTS (Xenial Xerus)"
wbash
muszli, a ostatniecho
właśnie drukuje pusty wiersz. Jakiej wersji systemu Linux i jakiej powłoki używasz? Niestety nie można wyświetlić sesji terminalu w komentarzu.Czasami zdarzało mi się, że metoda opisana w zaakceptowanej odpowiedzi nie działała, szczególnie jeśli separator jest znakiem powrotu karetki.
W tych przypadkach rozwiązałem w ten sposób:
źródło
read -a arr <<< "$strings"
nie działałemIFS=$'\n'
.Akceptowana odpowiedź działa dla wartości w jednym wierszu.
Jeśli zmienna ma kilka wierszy:
Potrzebujemy zupełnie innego polecenia, aby uzyskać wszystkie wiersze:
while read -r line; do lines+=("$line"); done <<<"$string"
Lub o wiele prostsza tablica bash :
Drukowanie wszystkich linii jest bardzo łatwe dzięki funkcji printf:
źródło
Jest to podobne do podejścia Jmoney38 , ale przy użyciu sed:
Wydruki 1
źródło
Kluczem do podziału łańcucha na tablicę jest wieloznakowy ogranicznik
", "
. Każde rozwiązanie stosowaneIFS
do ograniczników wieloznakowych jest z natury złe, ponieważ IFS jest zbiorem tych znaków, a nie ciągiem znaków.Jeśli przypiszesz,
IFS=", "
łańcuch zostanie przerwany na EITHER","
OR" "
lub dowolnej ich kombinacji, która nie jest dokładną reprezentacją dwóch znaków ogranicznika", "
.Możesz użyć
awk
lub,sed
aby podzielić ciąg, z podstawieniem procesu:Bardziej efektywne jest użycie wyrażenia regularnego bezpośrednio w Bash:
W drugiej formie nie ma podpowłoki i będzie ona z natury szybsza.
Edytuj przez bgoldst: Oto kilka testów porównujących moje
readarray
rozwiązanie z rozwiązaniem regex dawga , a także załączyłem rozwiązanie tego, coread
do cholery (uwaga: nieco zmodyfikowałem rozwiązanie regex dla większej harmonii z moim rozwiązaniem) (zobacz także moje komentarze poniżej Poczta):źródło
$BASH_REMATCH
. Działa i rzeczywiście unika tworzenia podpowłok. +1 ode mnie Jednak w wyniku krytyki sam regex jest trochę nieidealny, ponieważ wydaje się, że musiałeś zduplikować część tokena separatora (szczególnie przecinek), aby obejść brak wsparcia dla niechcianych multiplikatorów (także lookarounds) w ERE („rozszerzony” smak regularny wbudowany w bash). To sprawia, że jest trochę mniej ogólny i solidny.\n
rozdzielone linie tekstu) zawierająca te pola, więc katastrofalne spowolnienie prawdopodobnie nie nastąpiłoby. Jeśli masz ciąg z 100 000 pól - może Bash nie jest idealny ;-) Dzięki za test. Nauczyłem się czegoś lub dwóch.Rozwiązanie wieloznakowego separatora Pure Bash.
Jak zauważyli inni w tym wątku, pytanie OP podało przykład ciągu rozdzielanego przecinkami, który ma zostać przeanalizowany w tablicy, ale nie wskazało, czy był zainteresowany tylko ogranicznikami przecinkowymi, ogranicznikami jednoznakowymi lub znakami wieloznakowymi ograniczniki.
Ponieważ Google ma tendencję do umieszczania tej odpowiedzi na górze lub w pobliżu wyników wyszukiwania, chciałem zapewnić czytelnikom silną odpowiedź na pytanie o ograniczniki wielu znaków, ponieważ jest to również wspomniane w co najmniej jednej odpowiedzi.
Jeśli szukasz rozwiązania problemu z ogranicznikiem wieloznakowym, sugeruję przejrzenie postu Mallikarjun M. , w szczególności odpowiedzi od gniourf_gniourf, który zapewnia to eleganckie czyste rozwiązanie BASH za pomocą rozszerzenia parametrów:
Link do cytowanego komentarza / odnośnika
Link do cytowanego pytania: Jak rozdzielić ciąg na ograniczniku wieloznakowym w bash?
źródło
Działa to dla mnie w OSX:
Jeśli ciąg ma inny separator, po prostu 1. zastąp je spacją:
Prosty :-)
źródło
Innym sposobem na zrobienie tego bez modyfikacji IFS:
Zamiast zmieniać IFS tak, aby pasował do pożądanego separatora, możemy zastąpić wszystkie wystąpienia pożądanego separatora
", "
zawartością$IFS
via"${string//, /$IFS}"
.Może jednak będzie to bardzo powolne w przypadku bardzo dużych ciągów?
Jest to oparte na odpowiedzi Dennisa Williamsona.
źródło
Natrafiłem na ten post, gdy analizowałem dane wejściowe takie jak: słowo1, słowo2, ...
żadne z powyższych nie pomogło mi. rozwiązał to za pomocą awk. Jeśli to pomaga komuś:
źródło
Spróbuj tego
To proste. Jeśli chcesz, możesz również dodać deklarację (a także usunąć przecinki):
IFS został dodany w celu cofnięcia powyższego, ale działa bez niego w nowej instancji bash
źródło
Możemy użyć polecenia tr, aby podzielić ciąg znaków na obiekt tablicy. Działa zarówno w systemie MacOS, jak i Linux
Inną opcją jest użycie polecenia IFS
źródło
Użyj tego:
źródło
array=( $string )
to (niestety bardzo powszechne) antywzorzec projektowy: podział występuje słowo:string='Prague, Czech Republic, Europe'
; Występuje rozwinięciestring='foo[abcd],bar[efgh]'
nazwy ścieżki: zakończy się niepowodzeniem, jeśli masz plik o nazwie np.food
Lubbarf
w katalogu. Jedynym prawidłowym zastosowaniem takiej konstrukcji jeststring
glob.AKTUALIZACJA: Nie rób tego z powodu problemów z eval.
Z nieco mniejszą ceremonią:
na przykład
źródło
$
zmienną, a zobaczysz ... Piszę wiele skryptów i nigdy nie musiałem używać jednegoeval
Oto mój hack!
Dzielenie ciągów przez ciągi jest dość nudną rzeczą przy użyciu bash. Co się dzieje, mamy ograniczone podejścia, które działają tylko w kilku przypadkach (podzielone przez „;”, „/”, „.” Itd.) Lub mamy różne efekty uboczne w wynikach.
Poniższe podejście wymagało szeregu manewrów, ale wierzę, że zadziała w przypadku większości naszych potrzeb!
źródło
Dla elementów wielowarstwowych, dlaczego nie coś takiego
źródło
Innym sposobem byłoby:
Teraz twoje elementy są przechowywane w tablicy „arr”. Aby iterować po elementach:
źródło
eval
sztuczki). Twoje rozwiązanie pozostawia$IFS
po fakcie wartość przecinka.Ponieważ istnieje wiele sposobów rozwiązania tego problemu, zacznijmy od zdefiniowania tego, co chcemy zobaczyć w naszym rozwiązaniu.
readarray
do tego celu. Wykorzystajmy to.IFS
, zapętlenie, użycieeval
lub dodanie dodatkowego elementu, a następnie usunięcie go.readarray
Komenda jest najłatwiejszy w obsłudze z nowymi liniami jak ogranicznik. W przypadku innych ograniczników może dodać dodatkowy element do tablicy. Najczystsze podejście polega na tym, aby najpierw dostosować nasze dane wejściowe do formy, która działa dobrzereadarray
przed przekazaniem.Dane wejściowe w tym przykładzie nie mają ogranicznika wieloznakowego. Jeśli zastosujemy trochę zdrowego rozsądku, najlepiej będzie to rozumieć jako wejście oddzielone przecinkami, dla których każdy element może wymagać przycięcia. Moim rozwiązaniem jest podzielenie danych wejściowych przecinkiem na wiele linii, przycięcie każdego elementu i przekazanie go wszystkim
readarray
.źródło
Innym podejściem może być:
Po tym „arr” jest tablicą z czterema ciągami. Nie wymaga to zajmowania się IFS, czytaniem ani żadnymi innymi specjalnymi rzeczami, dlatego jest o wiele prostsze i bezpośrednie.
źródło