Come incorporare un comando shell in un'espressione sed?


16

Ho un file di testo con il seguente formato:

keyword value
keyword value
...

Dove parola chiave è una singola parola e il valore è tutto il resto fino alla fine della riga. Voglio leggere il file da uno script di shell, in modo che i valori (ma non le parole chiave) subiscano l'espansione della shell.

Con sed è facile abbinare le parole chiave e le parti di valore

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

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

che produce

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

ma poi la domanda è: come posso incorporare un comando shell nella parte sostitutiva dell'espressione sed. In questo caso, vorrei che la sostituzione fosse \1 `echo \2`.


Uhm ... Non sono così sicuro di dargli una risposta, ma usando DOUBLE citato con sed dovresti usare shell $ (comando) o $ variabili all'interno dell'espressione.
St0rM,

Risposte:


18

Sed standard non può chiamare una shell ( GNU sed ha un'estensione per farlo , se ti interessa solo Linux non incorporato), quindi dovrai fare un po 'dell'elaborazione al di fuori di sed. Esistono diverse soluzioni; tutti richiedono un preventivo accurato.

Non è chiaro esattamente come si desidera espandere i valori. Ad esempio, se una linea è

foo hello; echo $(true)  3

quale dei seguenti dovrebbe essere l'output?

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

Discuterò diverse possibilità di seguito.

guscio puro

È possibile ottenere la shell per leggere l'input riga per riga ed elaborarlo. Questa è la soluzione più semplice e anche la più veloce per i file brevi. Questa è la cosa più vicina alle tue esigenze " echo \2":

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

read -r keyword valueimposta $keywordsulla prima parola delimitata da spazi bianchi della riga e $valuesul resto della riga meno lo spazio bianco finale.

Se si desidera espandere i riferimenti alle variabili, ma non eseguire comandi al di fuori delle sostituzioni di comandi, inserire $valueun documento qui . Ho il sospetto che questo è quello che stavi davvero cercando.

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

sed convogliato in un guscio

È possibile trasformare l'input in uno script di shell e valutarlo. Sed è all'altezza del compito, anche se non è così facile. In linea con il tuo echo \2requisito " " (nota che dobbiamo evitare le virgolette singole nella parola chiave):

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

Andando con un documento qui, dobbiamo ancora sfuggire alla parola chiave (ma in modo diverso).

{
  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

Questo è il metodo più veloce se hai molti dati: non avvia un processo separato per ogni linea.

awk

Le stesse tecniche che abbiamo usato con sed funzionano con awk. Il programma risultante è notevolmente più leggibile. Andando con " echo \2":

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

Utilizzando un documento qui:

awk '
  NR==1 { print "cat <<EOF" }
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\\\$`/, "\\&", $1);
      print "k=<" kw "> v=<" $0 ">";
  }
  END { print "EOF" }
' | sh

Bella risposta. userò la soluzione di pura shell, poiché il file di input è davvero piccolo e le prestazioni non sono un problema, anche sono pulite e leggibili.
Ernest AC,

un po 'un trucco ma abbastanza pulito. ad es. usare sed per chiamare xxd per decodificare una lunga stringa esadecimale. . . gatto FtH.ch13 | sed -r 's /(.* text. *: [) ([0-9a-fA-F] *)] / \ 1 $ (echo \ 2 | xxd -r -p)] /; s / ^ ( . *) $ / echo "\ 1" / g '| bash> FtHtext.ch13 Dove FtH.ch13 ha righe come "test di testo esadecimale foo bar: [666f6f0a62617200]"
gaoithe,

14

Avendo GNU sedpuoi usare il seguente comando:

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

Quali uscite:

keyword value value
keyword value  value
keyword Linux

con i tuoi dati di input.

Spiegazione:

Il comando sed elimina l'output regolare usando l' -nopzione. -rviene passato per utilizzare espressioni regolari estese che ci consentono di evitare alcuni caratteri speciali nel modello ma non è necessario.

Il scomando viene utilizzato per trasferire la riga di input nel comando:

echo "\1" \2

La parola chiave get ha indicato il valore no. Passo l'opzione e- che è specifica per GNU - al scomando, che dice a sed di eseguire il risultato della sostituzione come comando di shell e di leggere i suoi risultati nel buffer di pattern (anche più righe). L'uso dell'opzione pafter (!) eConsente di sedstampare il buffer di pattern dopo l'esecuzione del comando.


Puoi fare a meno di entrambe le opzioni -ne pcioè sed -r 's/([^ ]+) (.*)/echo "\1" \2\n/e' input. Ma grazie per questo! Non sapevo edell'opzione.
Kaushal Modi,

@KaushalModi Oh sì, hai ragione! Sono seduto sul recinto quando si tratta di eopzione (introdotto da GNU). È ancora sed? :)
hek2mgl,

Bene, ha funzionato per me. È GNU sed di default per me (GNU sed versione 4.2.1) sulla distribuzione RHEL.
Kaushal Modi,

4

Puoi provare questo approccio:

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

(se capisco bene le tue intenzioni). Questo è inserire "process" all'inizio di ogni riga non vuota per renderlo uno script come:

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

da valutare ( eval) dove process è una funzione che stampa il messaggio previsto.


1

Se una soluzione non sed è accettabile, questo frammento PERL farà il lavoro:

$ echo "$input" | perl -ne 'chomp; /^\s*(.+?)\s+(.+)$/ && do { $v=`echo "$2"`; chomp($v); print "k=<$1> v=<$v>\n"}'

1
grazie, ma preferirei evitare di usare un altro linguaggio di scripting se posso e mantenerlo ai comandi unix standard e bourne shell
Ernest AC

0

SOLO PURO BACIO PURATO SED

lo farò

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

e funziona.


Potresti spiegare come funziona?
elysch,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.