Jak osadzić polecenie powłoki w wyrażeniu sed?

16

Mam plik tekstowy w następującym formacie:

keyword value
keyword value
...

Gdzie słowo kluczowe jest pojedynczym słowem, a wartość to wszystko inne do końca wiersza. Chcę odczytać plik ze skryptu powłoki, w taki sposób, aby wartości (ale nie słowa kluczowe) podlegały rozszerzaniu powłoki.

Z sed łatwo dopasować słowa kluczowe i wartościować części

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

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

który produkuje

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

ale pytanie brzmi: w jaki sposób mogę osadzić polecenie powłoki w zastępczej części wyrażenia sed. W tym przypadku chciałbym, aby zastąpienie było \1 `echo \2`.

Ernest AC
źródło
Uhm ... Nie jestem pewien, czy podam odpowiedź, ale użycie PODWÓJNEGO cudzysłowu z sedem powinno pozwolić na użycie powłoki $ (polecenie) lub zmiennych $ w wyrażeniu.
St0rM

Odpowiedzi:

18

Standardowy sed nie może wywoływać powłoki ( GNU sed ma do tego rozszerzenie , jeśli zależy ci tylko na niewbudowanym Linuksie), więc będziesz musiał wykonać część przetwarzania poza sed. Istnieje kilka rozwiązań; wszystkie wymagają starannego cytowania.

Nie jest jasne, w jaki sposób chcesz rozszerzyć wartości. Na przykład, jeśli linia to

foo hello; echo $(true)  3

który z poniższych wyników powinien być?

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

Omówię kilka możliwości poniżej.

czysta skorupa

Możesz uzyskać powłokę do odczytu linii wejściowej po linii i przetworzenia jej. Jest to najprostsze rozwiązanie, a także najszybsze w przypadku krótkich plików. To jest najbliższe wymaganiu „ echo \2”:

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

read -r keyword valueustawia $keywordna pierwsze słowo linii rozdzielane spacjami, a $valuena resztę linii minus końcowe spacje.

Jeśli chcesz rozwinąć odwołania do zmiennych, ale nie wykonujesz poleceń poza podstawieniami poleceń, umieść $valuew tym dokumencie dokument . Podejrzewam, że tego właśnie tak naprawdę szukałeś.

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

sed wpięty do muszli

Możesz przekształcić dane wejściowe w skrypt powłoki i to ocenić. Sed spełnia swoje zadanie, choć nie jest to takie proste. Zgodnie z wymaganiem „ echo \2” (pamiętaj, że musimy unikać pojedynczych cudzysłowów w słowie kluczowym):

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

Przechodząc do dokumentu tutaj, nadal musimy uciec od słowa kluczowego (ale inaczej).

{
  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

Jest to najszybsza metoda, jeśli masz dużo danych: nie rozpoczyna osobnego procesu dla każdej linii.

awk

Te same techniki, których używaliśmy przy sed, działają z awk. Powstały program jest znacznie bardziej czytelny. Idąc z „ echo \2”:

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

Korzystanie z dokumentu tutaj:

awk '
  NR==1 { print "cat <<EOF" }
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\\\$`/, "\\&", $1);
      print "k=<" kw "> v=<" $0 ">";
  }
  END { print "EOF" }
' | sh
Gilles „SO- przestań być zły”
źródło
świetna odpowiedź. Użyję rozwiązania opartego na czystej powłoce, ponieważ plik wejściowy jest rzeczywiście niewielki, a wydajność nie stanowi problemu, a także jest czysty i czytelny.
Ernest AC
trochę hack, ale dość porządnie. np. użyj sed, aby wywołać xxd, aby zdekodować długi ciąg szesnastkowy. . . kot FtH.ch13 | sed -r 's /(.* text. *: [) ([0-9a-fA-F] *)] / \ 1 $ (echo \ 2 | xxd -r -p)] /; s / ^ ( . *) $ / echo "\ 1" / g '| bash> FtHtext.ch13 Gdzie FtH.ch13 ma linie takie jak "test tekstu szesnastkowego foo bar: [666f6f0a62617200]"
Gaoithe
14

Mając GNU sedmożesz użyć następującego polecenia:

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

Które wyjścia:

keyword value value
keyword value  value
keyword Linux

z danymi wejściowymi.

Wyjaśnienie:

Polecenie sed pomija regularne wyjście za pomocą -nopcji. -rjest przekazywany w celu użycia rozszerzonych wyrażeń regularnych, co oszczędza nam ucieczki od specjalnych znaków w szyku, ale nie jest to wymagane.

sPolecenie jest wykorzystywany do przesyłania linii wejściowej do polecenia:

echo "\1" \2

Słowo kluczowe get podało wartość not. Podaję opcję e- która jest specyficzna dla GNU - skomendzie, która każe sedowi wykonać wynik podstawienia jako komendę powłoki i czytam jego wyniki w buforze wzorców (nawet wiele linii). Użycie opcji ppo (!) ePowoduje sedwydrukowanie bufora wzorów po wykonaniu polecenia.

hek2mgl
źródło
Możesz obejść się bez opcji -ni, ptj sed -r 's/([^ ]+) (.*)/echo "\1" \2\n/e' input. Ale dzięki za to! Nie wiedziałem o tej eopcji.
Kaushal Modi
@KaushalModi O tak, masz rację! Siedzę na płocie, jeśli chodzi o eopcję (wprowadzoną przez GNU). Czy to nadal sed? :)
hek2mgl
Cóż, zadziałało dla mnie. Domyślnie jest to GNU sed (GNU sed wersja 4.2.1) w dystrybucji RHEL.
Kaushal Modi
4

Możesz spróbować tego podejścia:

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')"

(jeśli dobrze zrozumiem twoją intencję). To znaczy wstaw „proces” na początku każdego niepustego wiersza, aby uczynić go skryptem:

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

do oceny ( eval), gdzie proces jest funkcją, która drukuje oczekiwany komunikat.

Stéphane Chazelas
źródło
1

Jeśli akceptowalne jest rozwiązanie inne niż sed, ten fragment PERL wykona zadanie:

$ echo "$input" | perl -ne 'chomp; /^\s*(.+?)\s+(.+)$/ && do { $v=`echo "$2"`; chomp($v); print "k=<$1> v=<$v>\n"}'
terdon
źródło
1
dzięki, ale wolę unikać używania innego języka skryptowego, jeśli mogę, i trzymam go przy standardowych poleceniach unixowych i powłoce Bourne'a
Ernest AC
0

TYLKO KISS KRÓTKI CZYSTY SED

zrobię to

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

i działa.

mr.tee
źródło
Czy możesz wyjaśnić, jak to działa?
elysch