Podziel ciąg według separatora i uzyskaj N-ty element

75

Mam ciąg:

one_two_three_four_five

Muszę zapisać w Awartości zmiennej twoi Bwartości zmiennej fourz powyższego ciągu

Alex
źródło

Odpowiedzi:

106

Użyj cutz _jako separatora pól i uzyskaj żądane pola:

A="$(cut -d'_' -f2 <<<'one_two_three_four_five')"
B="$(cut -d'_' -f4 <<<'one_two_three_four_five')"

Możesz także użyć echoi potoku zamiast ciągu Tutaj:

A="$(echo 'one_two_three_four_five' | cut -d'_' -f2)"
B="$(echo 'one_two_three_four_five' | cut -d'_' -f4)"

Przykład:

$ s='one_two_three_four_five'

$ A="$(cut -d'_' -f2 <<<"$s")"
$ echo "$A"
two

$ B="$(cut -d'_' -f4 <<<"$s")"
$ echo "$B"
four
heemayl
źródło
Czy jest jakaś alternatywa? Korzystam z ksh (nie bsh) i zwraca ksh: błąd składni: `<nieoczekiwany
Alex
@Alex Sprawdź moje zmiany.
heemayl
Dobra odpowiedź, mam małe pytanie: co się stanie, jeśli zmienna „$ s” jest folderem ścieżki? Kiedy próbuję wyciąć folder ścieżki, podoba mi się: `$ FILE = mój_użytkownik / mój_folder / [plik] *` $ echo $FILE my_user/my_folder/file.csv $ A="$(cut -d'/' -f2 <<<"$FILE")" $ echo $A [file]* Czy wiesz, co się tutaj dzieje?
Henry Navarro,
1
A jeśli chcesz tylko ostatnie pole, używając tylko wbudowanych powłok - bez konieczności określania jego pozycji lub gdy nie znasz liczby pól:echo "${s##*_}"
Amit Naidu
19

Używając tylko konstruktorów sh POSIX, możesz używać konstrukcji podstawiania parametrów do parsowania jednego separatora na raz. Zauważ, że ten kod zakłada, że ​​istnieje wymagana liczba pól, w przeciwnym razie ostatnie pole zostanie powtórzone.

string='one_two_three_four_five'
remainder="$string"
first="${remainder%%_*}"; remainder="${remainder#*_}"
second="${remainder%%_*}"; remainder="${remainder#*_}"
third="${remainder%%_*}"; remainder="${remainder#*_}"
fourth="${remainder%%_*}"; remainder="${remainder#*_}"

Alternatywnie, możesz użyć niecytowanego podstawienia parametru z wyłączonym rozszerzaniem symboli wieloznacznych i IFSustawionym na znak separatora (działa to tylko wtedy, gdy separator jest pojedynczym znakiem spacji lub dowolna sekwencja białych znaków jest separatorem).

string='one_two_three_four_five'
set -f; IFS='_'
set -- $string
second=$2; fourth=$4
set +f; unset IFS

Spowoduje to zablokowanie parametrów pozycji. Jeśli zrobisz to w funkcji, wpływa to tylko na parametry pozycyjne funkcji.

Jeszcze innym podejściem jest użycie readwbudowanego.

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF
Gilles
źródło
Użycie unset IFSnie powraca IFSdo wartości domyślnych. Jeśli później ktoś to zrobi, OldIFS="$IFS"będzie miał wartość zerową w OldIFS. Zakłada się również, że poprzednia wartość IFS jest wartością domyślną, co jest bardzo możliwe (i przydatne), że nie będzie. Jedynym prawidłowym rozwiązaniem jest przechowywanie, old="$IFS"a później przywracanie z IFS = „$ old”. Lub ... użyj podpowłoki (...). Albo jeszcze lepiej przeczytaj moją odpowiedź.
sorontar
@sorontar unset IFSnie przywraca IFSwartości domyślnej, ale przywraca podział pola do domyślnego efektu. Tak, jest to ograniczenie, ale w praktyce jest zwykle dopuszczalne. Problem z podpowłoką polega na tym, że musimy wyciągnąć z niej dane. Pokazuję rozwiązanie, które nie zmienia stanu na końcu read. (Działa w powłokach POSIX, ale IIRC nie w powłoce Bourne'a, ponieważ działałby readw podpowłoce z powodu dokumentu tutaj.) Użycie <<<odpowiedzi w tobie jest wariantem, który działa tylko w ksh / bash / zsh.
Gilles
Nie widzę problemu nawet z powłoką att lub pamiątką dotyczącą podpowłoki. Wszystkie testowane muszle (w tym stara burne) podają poprawną wartość w głównej powłoce.
sorontar
Co się stanie, jeśli moja ścieżka jest podobna user/my_folder/[this_is_my_file]*? Po tych krokach otrzymuję[this_is_my_file]*
Henry Navarro,
@HenryNavarro To wyjście nie odpowiada żadnemu fragmentowi kodu w mojej odpowiedzi. Żadne z nich nie robi nic specjalnego /.
Gilles,
17

Chciałem zobaczyć awkodpowiedź, więc oto jedna:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')
Paul Evans
źródło
1
A jeśli chcesz ostatni kawałek - bez konieczności określania jego pozycji lub gdy nie znasz liczby pól:awk -F_ '{print $NF}' <<< 'one_two_3_4_five'
Amit Naidu
8

Najprostszym sposobem (dla muszli z <<<) jest:

 IFS='_' read -r a second a fourth a <<<"$string"

Używanie zmiennej czasowej $azamiast $_ponieważ jedna powłoka narzeka.

W pełnym skrypcie:

 string='one_two_three_four_five'
 IFS='_' read -r a second a fourth a <<<"$string"
 echo "$second $fourth"

Bez zmian IFS, nie ma problemów z set -f(rozwinięcie nazwy ścieżki) Brak zmian parametrów pozycyjnych („$ @”).


W przypadku rozwiązania przenośnego dla wszystkich powłok (tak, wszystkie POSIX włącznie) bez zmiany IFS lub set -fużyj (nieco bardziej złożonego) odpowiednika heredoc:

string='one_two_three_four_five'

IFS='_' read -r a second a fourth a <<-_EOF_
$string
_EOF_

echo "$second $fourth"

Zrozum, że te rozwiązania (zarówno tutaj-doc, jak i użycie <<<usunie wszystkie końcowe znaki nowej linii.
I że jest to zaprojektowane dla zmiennej zawartości „jednej linii”.
Rozwiązania dla wielu linii są możliwe, ale wymagają bardziej złożonych konstrukcji.


Bardzo proste rozwiązanie jest możliwe w wersji bash 4.4

readarray -d _ -t arr <<<"$string"

echo "array ${arr[1]} ${arr[3]}"   # array numbers are zero based.

Nie ma odpowiednika dla powłok POSIX, ponieważ wiele powłok POSIX nie ma tablic.

W przypadku powłok posiadających tablice mogą być tak proste, jak:
(testowane w attsh, lksh, mksh, ksh i bash)

set -f; IFS=_; arr=($string)

Ale z dużą ilością dodatkowej instalacji hydraulicznej, aby zachować i zresetować zmienne i opcje:

string='one_* *_three_four_five'

case $- in
    *f*) noglobset=true; ;;
    *) noglobset=false;;
esac

oldIFS="$IFS"

set -f; IFS=_; arr=($string)

if $noglobset; then set -f; else set +f; fi

echo "two=${arr[1]} four=${arr[3]}"

W Zsh tablice zaczynają się od 1 i domyślnie nie dzielą łańcucha.
Tak więc należy wprowadzić pewne zmiany, aby działało to w Zsh.

sorontar
źródło
rozwiązania, które wykorzystują, read są proste, o ile OP nie chce wyodrębnić 76. i 127. elementu z długiego łańcucha ...
don_crissti
@don_crissti No tak, oczywiście, ale podobny konstrukt: readarrayw tej sytuacji może być łatwiejszy w użyciu.
sorontar
@don_crissti Dodałem również rozwiązanie tablicowe dla powłok, które mają tablice. W przypadku powłok POSIX, no cóż, nie posiadając tablic, parametry pozycyjne do 127 elementów nie są żadnym „prostym” rozwiązaniem.
sorontar
2

Dzięki zshmożesz podzielić ciąg (on _) na tablicę:

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

a następnie uzyskać dostęp do każdego elementu za pomocą indeksu tablicy:

print -r ${elements[4]}

Należy pamiętać, że w zsh(w przeciwieństwie do ksh/ bash) indeksy tablicowe zaczynają się od 1 .

don_crissti
źródło
Pamiętaj, aby dodać set -fostrzeżenie do pierwszego rozwiązania. ... *może gwiazdki ?
sorontar
@sorontar - dlaczego według ciebie potrzebuję set -f? Nie używam read/ IFS. Wypróbuj moje rozwiązania z ciągiem podobnym *_*_*lub czymkolwiek ...
don_crissti
Nie dla zsh, ale użytkownik poprosił o rozwiązanie ksh, więc może spróbować użyć go w tej powłoce. Ostrzeżenie pomoże mu uniknąć problemu.
sorontar
1

Czy rozwiązanie python jest dozwolone?

# python -c "import sys; print sys.argv[1].split('_')[1]" one_two_three_four_five
two

# python -c "import sys; print sys.argv[1].split('_')[3]" one_two_three_four_five
four
FHGD
źródło
Nie. Złe złe odpowiedzi
Raj Kumar
0

Kolejny przykład awk; prostsze do zrozumienia.

A=\`echo one_two_three_four_five | awk -F_ '{print $1}'\`  
B=\`echo one_two_three_four_five | awk -F_ '{print $2}'\`  
C=\`echo one_two_three_four_five | awk -F_ '{print $3}'\`  
... and so on...  

Może być również używany ze zmiennymi.
Załóżmy, że:
this_str = "one_two_three_four_five"
Następnie działają następujące elementy:
A = `echo $ {this_str} | awk -F_ '{print $ 1}' '
B = `echo $ {this_str} | awk -F_ '{print $ 2}' '
C = `echo $ {this_str} | awk -F_ '{print $ 3}' '
... i tak dalej ...

użytkownik274900
źródło