Teilen Sie den String nach dem Trennzeichen und erhalten Sie das N-te Element


75

Ich habe eine Zeichenfolge:

one_two_three_four_five

Ich muss in einem variablen AWert twound im variablen BWert fourvon der oben genannten Zeichenkette speichern

Antworten:


106

Verwenden Sie cutmit _als Feldtrennzeichen und erhalten gewünschten Felder:

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

Sie können echoanstelle des Here-Strings auch and pipe verwenden:

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

Beispiel:

$ s='one_two_three_four_five'

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

$ B="$(cut -d'_' -f4 <<<"$s")"
$ echo "$B"
four

Gibt es eine Alternative? Ich verwende ksh (nicht bsh) und es gibt ksh zurück: Syntaxfehler: "<" unerwartet
Alex

@Alex Überprüfe meine Änderungen.
Heemayl

Gute Antwort, ich habe eine kleine Frage: Was passiert, wenn Ihre Variable "$ s" ein Pfadordner ist? Wenn ich versuche, einen Pfadordner zu schneiden, gehe ich folgendermaßen vor: `$ FILE = mein_Benutzer / mein_Ordner / [Datei] *` $ echo $FILE my_user/my_folder/file.csv $ A="$(cut -d'/' -f2 <<<"$FILE")" $ echo $A [file]* Wissen Sie, was hier passiert?
Henry Navarro

1
Und wenn Sie nur das letzte Feld verwenden möchten, verwenden Sie nur Shell-Builtins - ohne die Position angeben zu müssen oder wenn Sie die Anzahl der Felder nicht kennen:echo "${s##*_}"
Amit Naidu

19

Mit nur POSIX sh Konstrukten, können Sie Parameter Substitution baut einen Begrenzer zu einem Zeitpunkt , zu analysieren. Beachten Sie, dass dieser Code davon ausgeht, dass die erforderliche Anzahl von Feldern vorhanden ist, andernfalls wird das letzte Feld wiederholt.

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

Alternativ können Sie eine nicht in Anführungszeichen gesetzte Parametersubstitution mit deaktivierter Platzhaltererweiterung verwenden und IFSdas Trennzeichen verwenden (dies funktioniert nur, wenn das Trennzeichen ein einzelnes Zeichen ohne Leerzeichen ist oder wenn eine Leerzeichenfolge ein Trennzeichen ist).

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

Dadurch werden die Positionsparameter überlastet. Wenn Sie dies in einer Funktion tun, sind nur die Positionsparameter der Funktion betroffen.

Ein weiterer Ansatz ist die Verwendung des readeingebauten.

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF

Die Verwendung von unset IFSkehrt nicht IFSzum Standard zurück. Wenn danach jemand OldIFS="$IFS"einen Nullwert in OldIFS hat. Außerdem wird davon ausgegangen, dass der vorherige Wert von IFS der Standardwert ist, was durchaus möglich (und sinnvoll) ist, nicht zu sein. Die einzig richtige Lösung ist das Speichern old="$IFS"und spätere Wiederherstellen mit IFS = "$ old". Oder ... eine Unterschale verwenden (...). Oder besser noch, lies meine Antwort.
Sorontar

@sorontar unset IFSwird nicht IFSauf den Standardwert zurückgesetzt, aber die Feldaufteilung wird auf den Standardeffekt zurückgesetzt. Ja, dies ist eine Einschränkung, in der Praxis jedoch in der Regel akzeptabel. Das Problem mit einer Subshell ist, dass wir Daten daraus holen müssen. Ich zeige eine Lösung, die den Zustand am Ende nicht ändert, mit read. (Es funktioniert in POSIX-Shells, aber IIRC nicht in der Bourne-Shell, da es readaufgrund des Here-Dokuments in einer Subshell ausgeführt werden würde .) Die Verwendung von <<<as in you answer ist eine Variante, die nur in ksh / bash / zsh funktioniert.
Gilles

Ich sehe kein Problem, auch nicht mit einer Att- oder Erbstückshell über eine Subshell. Alle getesteten Shells (einschließlich des alten Bournes) liefern den korrekten Wert in der Haupt-Shell.
Sorontar

Was passiert, wenn mein Weg ungefähr so ​​ist user/my_folder/[this_is_my_file]*? Was ich erhalte, wenn ich diesen Schritten folge, ist[this_is_my_file]*
Henry Navarro

@HenryNavarro Diese Ausgabe entspricht keinem der Codeausschnitte in meiner Antwort. Keiner von ihnen tut etwas Besonderes /.
Gilles

17

Wollte eine awkAntwort sehen, also hier ist eine:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')

1
Und wenn Sie das letzte Stück wollen - ohne Angabe der Position oder wenn Sie die Anzahl der Felder nicht kennen:awk -F_ '{print $NF}' <<< 'one_two_3_4_five'
Amit Naidu

8

Der einfachste Weg (für Shells mit <<<) ist:

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

Verwenden einer temporalen Variablen, $aanstatt sich über $_eine Shell zu beschweren.

In einem vollständigen Skript:

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

Keine Änderung des IFS, keine Probleme mit set -f(Pfadnamenerweiterung) Keine Änderung der Positionsparameter ("$ @").


Für eine Lösung, die auf alle Shells portierbar ist (ja, alle POSIX-Versionen enthalten), ohne IFS zu ändern, oder set -fverwenden Sie das (etwas komplexere) heredoc-Äquivalent:

string='one_two_three_four_five'

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

echo "$second $fourth"

Verstehen Sie, dass diese Lösung (sowohl das Here-Doc als auch die Verwendung von <<<werden alle nachgestellten Zeilenumbrüche entfernen.
Und dass dies auf einen " einzeiligen " variablen Inhalt ausgelegt ist.
Lösungen für Mehrzeilige sind möglich, erfordern jedoch komplexere Konstrukte.


Eine sehr einfache Lösung ist in der Bash-Version 4.4 möglich

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

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

Es gibt keine Entsprechung für POSIX-Shells, da viele POSIX-Shells keine Arrays haben.

Für Shells mit Arrays gilt möglicherweise
Folgendes : (getestetes Arbeiten in attsh, lksh, mksh, ksh und bash)

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

Aber mit einer Menge zusätzlicher Klempnerarbeiten zum Speichern und Zurücksetzen von Variablen und Optionen:

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]}"

In zsh beginnen Arrays mit 1 und teilen die Zeichenfolge nicht standardmäßig.
Es müssen also einige Änderungen vorgenommen werden, damit dies in zsh funktioniert.


Lösungen, die verwendet read werden , sind einfach, solange OP nicht das 76. und 127. Element aus einer langen Zeichenfolge extrahieren möchte ...
don_crissti

@don_crissti Nun ja, natürlich, aber ein ähnliches Konstrukt: readarraykönnte für diese Situation einfacher zu verwenden sein.
Sorontar

@don_crissti Ich habe auch eine Array-Lösung für Shells mit Arrays hinzugefügt. Nun, für POSIX-Shells, die keine Arrays haben, sind Positionsparameter bis zu 127 Elementen in keiner Weise eine "einfache" Lösung.
Sorontar

2

Mit zshkönnten Sie den String (on _) in ein Array aufteilen :

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

und dann auf jedes Element über den Array-Index zugreifen:

print -r ${elements[4]}

Beachten Sie, dass in zsh(im Gegensatz zu ksh/ bash) Array-Indizes bei 1 beginnen .


Bitte denken Sie daran set -f, der ersten Lösung eine Warnung hinzuzufügen . ... *vielleicht Sternchen ?
Sorontar

@sorontar - warum glaubst du, brauche ich set -f? Ich benutze read/ nicht IFS. Versuchen Sie meine Lösungen mit einer Zeichenfolge wie *_*_*oder was auch immer ...
don_crissti

Nicht für zsh, aber der Benutzer hat nach einer ksh-Lösung gefragt. Daher kann er versuchen, sie in dieser Shell zu verwenden. Eine Warnung hilft ihm, das Problem zu vermeiden.
Sorontar

1

Ist eine Python-Lösung erlaubt?

# 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

Nein, schlecht, schlechte Antwort
Raj Kumar

0

Ein anderes awk Beispiel; einfacher zu verstehen.

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...  

Kann auch mit Variablen verwendet werden.
Nehmen wir an:
this_str = "one_two_three_four_five"
Dann funktioniert folgendes:
A = `echo $ {this_str} | awk -F_ '{print $ 1}' `
B =` echo $ {this_str} | awk -F_ '{print $ 2}' `
C =` echo $ {this_str} | awk -F_ '{print $ 3}' `
... und so weiter ...

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.