Wie binde ich einen Shell-Befehl in einen sed-Ausdruck ein?

16

Ich habe eine Textdatei mit folgendem Format:

keyword value
keyword value
...

Wobei das Schlüsselwort ein einzelnes Wort und der Wert alles andere bis zum Zeilenende ist. Ich möchte die Datei aus einem Shell-Skript so lesen, dass die Werte (aber nicht die Schlüsselwörter) eine Shell-Erweiterung erfahren.

Mit sed ist es einfach, die Schlüsselwörter und Wertteile abzugleichen

input='
keyword value value
keyword "value  value"
keyword `uname`
'

echo "$input"|sed -e 's/^\([^[:space:]]*\)[[:space:]]\(.*\)$/k=<\1> v=<\2>/'

was produziert

k=<keyword> v=<value value>
k=<keyword> v=<"value  value">
k=<keyword> v=<`uname`>

Dann stellt sich die Frage, wie ich einen Shell-Befehl in den Ersatzteil des sed-Ausdrucks einbetten kann. In diesem Fall möchte ich den Ersatz haben \1 `echo \2`.

Ernest AC
quelle
Ähm ... Ich bin mir nicht so sicher, ob ich es als Antwort geben soll, aber wenn Sie DOUBLE in Anführungszeichen mit sed verwenden, sollten Sie Shell $ (Befehl) oder $ -Variablen innerhalb des Ausdrucks verwenden.
27.

Antworten:

18

Standard sed kann keine Shell aufrufen ( GNU sed hat eine Erweiterung , wenn Sie sich nur für nicht eingebettetes Linux interessieren), daher müssen Sie einen Teil der Verarbeitung außerhalb von sed ausführen. Es gibt verschiedene Lösungen. Alle erfordern sorgfältiges Zitieren.

Es ist nicht genau klar, wie die Werte erweitert werden sollen. Zum Beispiel, wenn eine Linie ist

foo hello; echo $(true)  3

Welche der folgenden Angaben sollte die Ausgabe sein?

k=<foo> value=<hello; echo   3>
k=<foo> value=<hello; echo   3>
k=<foo> value=<hello; echo 3>
k=<foo> value=<foo hello
  3>

Ich werde im Folgenden einige Möglichkeiten diskutieren.

reine Schale

Sie können die Shell veranlassen, die Eingabe zeilenweise zu lesen und zu verarbeiten. Dies ist die einfachste und auch die schnellste Lösung für kurze Dateien. Dies kommt Ihrer Anforderung " echo \2" am nächsten :

while read -r keyword value; do
  echo "k=<$keyword> v=<$(eval echo "$value")>"
done

read -r keyword valueSetzt $keywordauf das erste durch Leerzeichen getrennte Wort der Zeile und $valueauf den Rest der Zeile abzüglich des nachgestellten Leerzeichens.

Wenn Sie Variablenverweise erweitern möchten, aber keine Befehle außerhalb von Befehlsersetzungen ausführen möchten, fügen Sie sie $valuein ein Here-Dokument ein . Ich vermute, dass Sie genau danach gesucht haben.

while read -r keyword value; do
  echo "k=<$keyword> v=<$(cat <<EOF
$value
EOF
)>"
done

sed in eine Muschel gepumpt

Sie können die Eingabe in ein Shell-Skript umwandeln und auswerten. Sed ist der Aufgabe gewachsen, auch wenn es nicht so einfach ist. Entsprechend Ihrer " echo \2" Anforderung (beachten Sie, dass das Keyword in einfachen Anführungszeichen stehen muss):

sed  -e 's/^ *//' -e 'h' \
     -e 's/[^ ]*  *//' -e 'x' \
     -e 's/ .*//' -e "s/'/'\\\\''/g" -e "s/^/echo 'k=</" \
     -e 'G' -e "s/\n/>' v=\\</" -e 's/$/\\>/' | sh

Wenn Sie ein Here-Dokument verwenden, müssen Sie das Schlüsselwort immer noch umgehen (aber anders).

{
  echo 'cat <<EOF'
  sed -e 's/^ */k=</' -e 'h' \
      -e 's/[^ ]*  *//' -e 'x' -e 's/ .*//' -e 's/[\$`]/\\&/g' \
      -e 'G' -e "s/\n/> v=</" -e 's/$/>/'
  echo 'EOF'
 } | sh

Dies ist die schnellste Methode, wenn Sie viele Daten haben: Es wird nicht für jede Zeile ein separater Prozess gestartet.

awk

Die gleichen Techniken, die wir bei sed angewendet haben, arbeiten mit awk. Das resultierende Programm ist deutlich lesbarer. Gehen mit " echo \2":

awk '
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\047/, "\047\\\047\047", $1);
      print "echo \047k=<" kw ">\047 v=\\<" $0 "\\>";
  }' | sh

Verwenden eines Here-Dokuments:

awk '
  NR==1 { print "cat <<EOF" }
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\\\$`/, "\\&", $1);
      print "k=<" kw "> v=<" $0 ">";
  }
  END { print "EOF" }
' | sh
Gilles 'SO - hör auf böse zu sein'
quelle
gute Antwort. Ich werde die reine Shell-Lösung verwenden, da die Eingabedatei in der Tat klein ist und die Leistung kein Problem darstellt. Außerdem ist sie sauber und lesbar.
Ernest AC
ein bisschen hacken, aber ordentlich genug. zB benutze sed, um xxd aufzurufen, um eine lange Hex-Zeichenkette zu dekodieren. . . Katze FtH.ch13 | sed -r 's /(.* text. *: [) ([0-9a-fA-F] *)] / \ 1 $ (echo \ 2 | xxd -r -p)] /; s / ^ ( . *) $ / echo "\ 1" / g '| bash> FtHtext.ch13 Wobei FtH.ch13 Zeilen wie "foo bar hex text test: [666f6f0a62617200]" enthält
gaoithe
14

Mit GNU können sedSie den folgenden Befehl verwenden:

sed -nr 's/([^ ]+) (.*)/echo "\1" \2\n/ep' input

Welche Ausgänge:

keyword value value
keyword value  value
keyword Linux

mit Ihren Eingabedaten.

Erläuterung:

Der Befehl sed unterdrückt die reguläre Ausgabe mit der -nOption. -rwird übergeben, um erweiterte reguläre Ausdrücke zu verwenden, wodurch wir einige Sonderzeichen im Muster vermeiden, dies ist jedoch nicht erforderlich.

Mit dem sBefehl wird die Eingabezeile in den Befehl übertragen:

echo "\1" \2

Das Schlüsselwort wird nicht in Anführungszeichen gesetzt. Ich übergebe die Option e- die GNU-spezifisch ist - an den sBefehl, der sed anweist, das Ergebnis der Ersetzung als Shell-Befehl auszuführen und die Ergebnisse in den Musterpuffer zu lesen (auch mehrere Zeilen). Mit der Option pnach (!) eMacht sedden Musterpuffer Druck , nachdem der Befehl ausgeführt wurde.

hek2mgl
quelle
Sie können sowohl die -nund pOptionen dh verzichten sed -r 's/([^ ]+) (.*)/echo "\1" \2\n/e' input. Aber danke dafür! Ich wusste nicht über die eOption.
Kaushal Modi
@KaushalModi Oh ja, du hast recht! Ich sitze auf dem Zaun, wenn es um eOption geht (eingeführt von GNU). Ist das immer noch sed? :)
hek2mgl
Nun, es hat bei mir funktioniert. Es ist für mich standardmäßig GNU sed (GNU sed Version 4.2.1) auf RHEL-Distribution.
Kaushal Modi
4

Sie könnten diesen Ansatz ausprobieren:

input='
keyword value value
keyword "value  value"
keyword `uname`
'

process() {
  k=$1; shift; v="$*"
  printf '%s\n' "k=<$k> v=<$v>"
}

eval "$(printf '%s\n' "$input" | sed -n 's/./process &/p')"

(Wenn ich deine Absicht richtig verstanden habe). Das heißt, fügen Sie "process" am Anfang jeder nicht leeren Zeile ein, um daraus ein Skript wie das folgende zu machen:

process keyword value value
process keyword "value  value"
process keyword `uname`

zu evaluieren ( eval) wobei process eine Funktion ist, die die erwartete Nachricht ausgibt.

Stéphane Chazelas
quelle
1

Wenn eine nicht gesetzte Lösung akzeptabel ist, erledigt dieses PERL-Snippet die Aufgabe:

$ echo "$input" | perl -ne 'chomp; /^\s*(.+?)\s+(.+)$/ && do { $v=`echo "$2"`; chomp($v); print "k=<$1> v=<$v>\n"}'
terdon
quelle
1
danke, aber ich würde lieber vermeiden, eine andere Skriptsprache zu verwenden, wenn ich kann und es zu Standard-Unix-Befehlen und Bourneshell halten
Ernest AC
0

NUR KISS SHORT PURE SED

ich werde dies tun

echo "ls_me" | sed -e "s/\(ls\)_me/\1/e" -e "s/to be/continued/g;"

und es funktioniert.

mr.tee
quelle
Könnten Sie bitte erklären, wie es funktioniert?
Elysch