
In einem Bash-Skript habe ich die folgende Variable:
file_name='this_is_the_hart_part.csv'
Verwenden von
var2=$(echo $file_name | sed -e 's/_{2}\(.*\)_{3}/\1/')
Ich möchte die Teilzeichenfolge „the“ (zwischen den Unterstrichen 2 und 3 in der Variable $file_name) extrahieren.
Aber ich bekomme $var2 gleich $file_name zurück. Wie muss ich meinen sed-Befehl ändern?
Antwort1
Die von unterstützten Typen regulärer Ausdrücke sed
erlauben kein nicht-gieriges Matching mit *
.
Sie möchten das dritte _
durch -getrennte Feld erhalten. Am einfachsten geht das mit cut
:
cut -d '_' -f 3
Oder mit awk
:
awk -F '_' '{ print $3 }'
Oder in der Shell, indem Sie nacheinander die ersten beiden dieser Felder entfernen und dann das Ende abschneiden:
str=${file_name#*_}
str=${str#*_}
str=${str%%_*}
"$str"
wäre das Wort the
am Ende. Die Verwendung dieser letzten Variante wäre wahrscheinlich der schnellste und robusteste dieser drei Wege.
Die Variablensubstitution ${variable#*_}
würde zu einer Zeichenfolge führen, $variable
bei der das führende Bit bis einschließlich des ersten Unterstrichs entfernt wurde. Das ${variable%%_*}
würde alles vom ersten Unterstrich bis zum Ende von entfernen $variable
. Dies sind Standardvariablensubstitutionen.
Der Vorteil der Verwendung der Variablensubstitution auf einen Dateinamen besteht darin, dass sie mit Dateinamen mit Zeilenumbrüchen zurechtkommt, was weder mit awk
noch noch sed
oder cut
möglich wäre. Verwenden Sie im Allgemeinen keine zeilenorientierten Textbearbeitungstools für Dateinamen.
Außerdem verwenden Sie echo $file_name
. Da $file_name
nicht in Anführungszeichen steht, würde es einer Worttrennung unterzogen (bei jedem Zeichen, das auch Teil von ist $IFS
; standardmäßig ein Leerzeichen, ein Tabulator und ein Zeilenumbruch) und die generierten Wörter würden, wenn sie Dateinamen-Globbing-Zeichen enthalten, von der Shell mit Dateinamen im aktuellen Verzeichnis abgeglichen. Und Backslashs im Dateinamen können ebenfalls verschwinden oder unerwünschte Auswirkungen haben (selbst wenn Sie die Erweiterung in Anführungszeichen setzen). Die ksh
Shell würde auch Klammererweiterungen für den Wert von durchführen, $file_name
wenn dieser nicht in Anführungszeichen steht.
Antwort2
Erste Anmerkung: Das sed
ist einTextDienstprogramm, das standardmäßig jeweils nur eine Zeile bearbeitet, während Dateinamen beliebige Zeichen (einschließlich Zeilenumbrüche) und sogar Nicht-Zeichen (können Nicht-Text).
Auch,Das Verlassen einer Variable ohne Anführungszeichen hat eine ganz besondere Bedeutung, das will man fast nie, es ist auchpotentiell sehr gefährlich.
Auch,Sie können nicht echo
beliebige Daten ausgeben, verwenden Sie printf
stattdessen.
Außerdem lautet die Syntax für die Variablenzuweisung in Bourne-ähnlichen Shells: var=value
, nicht $var=value
.
Sie können die gesamte Ausgabe von echo
(oder besser printf
) in sed
den Musterbereich von laden mit:
printf '%s\n' "$filename" | sed -e :1 -e '$!{N;b1' -e '}'
Anschließend können Sie den Code hinzufügen, um den Teil zwischen dem zweiten und dritten zu extrahieren _
:
var2=$(
printf '%s\n' "$filename" |
sed -ne :1 -e '$!{N;b1' -e '}' -e 's/^\([^_]*_\)\{2\}\([^_]*\)_.*/\2/p'
)
Der nicht gierige Teil wird durch die Verwendung [^_]*
(einer Folge von Nicht- _
Zeichen) angegangen, die im Gegensatz zu .*
Garantien nicht über _
Grenzen hinaus übereinstimmt (obwohl sie in vielen Implementierungen immer noch an Nicht-Zeichen hängen bleiben würde).
In diesem Fall hier könnten Sie stattdessen Shell-Parametererweiterungsoperatoren verwenden:
case $filename in
(*_*_*_*) var2=${filename#*_*_}; var2=${var2%%_*};;
(*) var2=;;
esac
Dies würde besser funktionieren, wenn der Dateiname kein Text ist oder wenn der Teil, den Sie extrahieren möchten, mit einem Zeilenumbruchzeichen endet (und wäre auch effizienter).
Einige Shells mögen zsh
oder ksh93
haben erweiterte Operatoren:
zsh
:Aufteilen
_
und drittes Feld erhalten:var2=${"${(@s:_:)filename}"[3]}
Verwenden der
${var/pattern/replacement}
Rückverweise und (in diesem Fall müssen Sie zunächst überprüfen, ob die Variable mindestens drei Unterstriche enthält, da sonst keine Ersetzung erfolgt).set -o extendedglob var2=${filename/(#b)*_*_(*)_*/$match[1]}
ksh93
:var2=${filename/*_*_@(*)_*/\1}
Antwort3
@Kusalananda hat Recht, das sed
ist das falsche Tool und Sie können kein nicht-gieriges Matching durchführen. Aber Sie können einen Workaround für nicht-gieriges Matching verwenden:
[^_]*
wird jedes Zeichen abgleichen, das nicht_
In Ihrem Fall könnten Sie etwa Folgendes tun:
printf '%s\n' "$file_name" | sed -e 's/^[^_]*_[^_]*_\([^_]*\).*$/\1/g'
Aber … für deinen Anwendungsfall solltest du besser andere Tools verwenden …