Bagaimana cara menanamkan perintah shell ke ekspresi sed?

16

Saya memiliki file teks dengan format berikut:

keyword value
keyword value
...

Di mana kata kunci adalah satu kata dan nilai adalah segalanya sampai akhir baris. Saya ingin membaca file dari skrip shell, dengan cara nilai-nilai (tetapi bukan kata kunci) mengalami ekspansi shell.

Dengan sed mudah untuk mencocokkan kata kunci dan bagian nilai

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

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

yang menghasilkan

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

tapi kemudian pertanyaannya adalah bagaimana saya bisa menanamkan perintah shell ke bagian pengganti dari ekspresi sed. Dalam hal ini saya ingin menjadi pengganti \1 `echo \2`.

Ernest AC
sumber
Uhm ... Saya tidak SANGAT yakin untuk memberikannya sebagai jawaban, tetapi menggunakan DOUBLE yang dikutip dengan sed seharusnya membiarkan Anda menggunakan shell $ (perintah) atau $ variabel di dalam ekspresi.
St0rM

Jawaban:

18

Sed standar tidak dapat memanggil shell ( GNU sed memiliki ekstensi untuk melakukannya , jika Anda hanya peduli pada Linux yang tidak tertanam), jadi Anda harus melakukan beberapa pemrosesan di luar sed. Ada beberapa solusi; semua membutuhkan mengutip dengan cermat.

Tidak jelas bagaimana Anda ingin nilai diperluas. Misalnya, jika sebuah garis

foo hello; echo $(true)  3

mana dari berikut ini yang harus menjadi 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>

Saya akan membahas beberapa kemungkinan di bawah ini.

cangkang murni

Anda bisa mendapatkan shell untuk membaca input baris demi baris dan memprosesnya. Ini adalah solusi paling sederhana, dan juga yang tercepat untuk file pendek. Ini adalah hal yang paling dekat dengan kebutuhan Anda " echo \2":

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

read -r keyword valueset $keywordke kata pertama yang dibatasi spasi spasi, dan $valueke seluruh baris dikurangi spasi spasi.

Jika Anda ingin memperluas referensi variabel, tetapi tidak menjalankan perintah di luar pergantian perintah, masukkan ke $valuedalam dokumen di sini . Saya menduga ini adalah apa yang sebenarnya Anda cari.

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

sed disalurkan ke shell

Anda dapat mengubah input menjadi skrip shell dan mengevaluasinya. Sed sampai dengan tugas, meskipun itu tidak mudah. Mengikuti echo \2persyaratan " " Anda (perhatikan bahwa kami perlu menghindari tanda kutip tunggal dalam kata kunci):

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

Pergi dengan dokumen di sini, kita masih perlu melarikan diri kata kunci (tetapi berbeda).

{
  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

Ini adalah metode tercepat jika Anda memiliki banyak data: itu tidak memulai proses terpisah untuk setiap baris.

awk

Teknik yang sama kami gunakan dengan sed work dengan awk. Program yang dihasilkan jauh lebih mudah dibaca. Going with " echo \2":

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

Menggunakan dokumen di sini:

awk '
  NR==1 { print "cat <<EOF" }
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\\\$`/, "\\&", $1);
      print "k=<" kw "> v=<" $0 ">";
  }
  END { print "EOF" }
' | sh
Gilles 'SO- berhenti menjadi jahat'
sumber
jawaban yang bagus Saya akan menggunakan solusi shell murni, karena file input memang kecil dan kinerja tidak menjadi masalah, juga itu bersih dan mudah dibaca.
Ernest AC
sedikit retas tetapi cukup rapi. mis. gunakan sed untuk memanggil xxd untuk memecahkan kode string hex panjang. . . kucing FtH.ch13 | sed -r 's /(.* teks. *: [) ([0-9a-fA-F] *)] / \ 1 $ (echo \ 2 | xxd -r -p)] /; s / ^ ( . *) $ / echo "\ 1" / g '| bash> FtHtext.ch13 Di mana FtH.ch13 memiliki garis-garis seperti "foo bar hex text test: [666f6f0a62617200]"
gaoithe
14

Memiliki GNU sedAnda dapat menggunakan perintah berikut:

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

Output yang mana:

keyword value value
keyword value  value
keyword Linux

dengan data input Anda.

Penjelasan:

Perintah sed menekan output reguler menggunakan -nopsi. -rdilewatkan untuk menggunakan ekspresi reguler yang diperluas yang menyelamatkan kita dari lolosnya karakter khusus dalam pola tetapi tidak diperlukan.

The sPerintah ini digunakan untuk mentransfer baris masukan ke dalam perintah:

echo "\1" \2

Kata kunci get mengutip nilai bukan. Saya meneruskan opsi e- yang khusus GNU - ke sperintah, yang memberitahu sed untuk mengeksekusi hasil substitusi sebagai perintah shell dan membaca hasilnya ke dalam buffer pola (Bahkan beberapa baris). Menggunakan opsi pafter (!) eMembuat sedmencetak buffer pola setelah perintah dieksekusi.

hek2mgl
sumber
Anda dapat melakukannya tanpa kedua opsi -ndan pyaitu sed -r 's/([^ ]+) (.*)/echo "\1" \2\n/e' input. Tapi terima kasih untuk ini! Saya tidak tahu tentang eopsi itu.
Kaushal Modi
@ KaushalModi Oh ya, kamu benar! Saya duduk di pagar ketika datang ke eopsi (diperkenalkan oleh GNU). Apakah itu masih sed? :)
hek2mgl
Ya, itu berhasil untuk saya. Ini GNU sed secara default untuk saya (GNU sed versi 4.2.1) pada distribusi RHEL.
Kaushal Modi
4

Anda dapat mencoba pendekatan ini:

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

(jika saya benar niat Anda). Itu adalah memasukkan "proses" di awal setiap baris yang tidak kosong untuk menjadikannya skrip seperti:

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

untuk dievaluasi ( eval) di mana proses adalah fungsi yang mencetak pesan yang diharapkan.

Stéphane Chazelas
sumber
1

Jika solusi non-sed dapat diterima, cuplikan PERL ini akan melakukan pekerjaan:

$ echo "$input" | perl -ne 'chomp; /^\s*(.+?)\s+(.+)$/ && do { $v=`echo "$2"`; chomp($v); print "k=<$1> v=<$v>\n"}'
terdon
sumber
1
terima kasih tetapi saya lebih suka menghindari menggunakan bahasa skrip lain jika saya bisa dan tetap menggunakan perintah unix standar dan bourne shell
Ernest AC
0

HANYA CIUMAN SINGKAT MURNI SED

Saya akan melakukan ini

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

dan itu bekerja.

Tuan
sumber
Bisakah Anda jelaskan bagaimana cara kerjanya?
elysch