Como incorporar um comando shell em uma expressão sed?

14

Eu tenho um arquivo de texto com o seguinte formato:

keyword value
keyword value
...

Onde palavra-chave é uma única palavra e valor é todo o resto até o final da linha. Eu quero ler o arquivo de um script de shell, de uma forma que os valores (mas não as palavras-chave) passem por expansão de shell.

Com sed é fácil combinar as palavras-chave e partes de valor

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

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

que produz

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

mas a questão é como posso incorporar um comando shell na parte de substituição da expressão sed. Neste caso, gostaria que a substituição fosse 'echo ' .

    
por Ernest A C 16.09.2012 / 16:39

5 respostas

17

O sed padrão não pode chamar um shell ( O GNU sed tem uma extensão para fazê-lo , se você se importa apenas com Linux não embarcado), então você terá que fazer um pouco do processamento fora do sed. Existem várias soluções; todos exigem citações cuidadosas.

Não está claro exatamente como você deseja que os valores sejam expandidos. Por exemplo, se uma linha for

foo hello; echo $(true)  3

qual das seguintes opções deve ser a saída?

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

Vou discutir várias possibilidades abaixo.

casca pura

Você pode obter o shell para ler a linha de entrada por linha e processá-la. Esta é a solução mais simples e também a mais rápida para arquivos curtos. Essa é a coisa mais próxima do seu requisito “ echo ”:

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

read -r keyword value define $keyword para a primeira palavra delimitada por espaço em branco da linha e $value para o restante da linha menos o espaço em branco à direita.

Se você quiser expandir as referências de variáveis, mas não executar comandos fora das substituições de comandos, coloque $value dentro de um aqui documento . Eu suspeito que isso é o que você estava realmente procurando.

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

sed canalizado em um shell

Você pode transformar a entrada em um script de shell e avaliá-lo. Sed está à altura da tarefa, embora não seja tão fácil. Indo com o seu requisito " echo " (note que precisamos escapar de aspas simples na palavra-chave):

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

Indo com um documento aqui, ainda precisamos escapar da palavra-chave (mas de forma diferente).

{
  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

Este é o método mais rápido se você tiver muitos dados: ele não inicia um processo separado para cada linha.

awk

As mesmas técnicas que usamos com o trabalho sed com o awk. O programa resultante é consideravelmente mais legível. Indo com " echo ":

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

Usando um documento aqui:

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

Tendo o GNU sed , você pode usar o seguinte comando:

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

Quais resultados:

keyword value value
keyword value  value
keyword Linux

com seus dados de entrada.

Explicação:

O comando sed suprime a saída regular usando a opção -n . -r é passado para usar expressões regulares estendidas, o que nos poupa algum escape de caracteres especiais no padrão, mas isso não é necessário.

O comando s é usado para transferir a linha de entrada para o comando:

echo "" 

A palavra-chave get citou o valor não. Eu passo a opção e - que é específica do GNU - para o comando s , que diz ao sed para executar o resultado da substituição como um comando shell e ler seus resultados no buffer de padrões (até várias linhas). Usando a opção p after (!) e faz com que sed imprima o buffer padrão após o comando ter sido executado.

    
por 03.04.2015 / 17:20
4

Você pode tentar essa abordagem:

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 eu tiver sua intenção correta). Isso é inserir "processo" no início de cada linha não vazia para torná-lo um script como:

process keyword value value
process keyword "value  value"
process keyword 'uname'

a ser avaliado ( eval ) onde processo é uma função que imprime a mensagem esperada.

    
por 16.09.2012 / 17:48
1

Se uma solução não sed for aceitável, esse snippet PERL fará o trabalho:

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

SOMENTE BEIJO SHORT PURE SED

Eu farei isso

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

e está funcionando.

    
por 10.06.2018 / 15:05

Tags