Nicht gieriges Matching in sed

Nicht gieriges Matching in sed

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 sederlauben 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 theam 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, $variablebei 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 awknoch noch sedoder cutmöglich wäre. Verwenden Sie im Allgemeinen keine zeilenorientierten Textbearbeitungstools für Dateinamen.

Außerdem verwenden Sie echo $file_name. Da $file_namenicht 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 kshShell würde auch Klammererweiterungen für den Wert von durchführen, $file_namewenn dieser nicht in Anführungszeichen steht.

Antwort2

Erste Anmerkung: Das sedist 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 echobeliebige Daten ausgeben, verwenden Sie printfstattdessen.

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 sedden 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 zshoder ksh93haben 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 sedist 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 …

verwandte Informationen