Cara menggunakan `/ usr / bin / env sed -f` di shebang?

25

Mengetik /usr/bin/env sed -fdalam pekerjaan terminal.

Tetapi jika menggunakannya sebagai shebang,

#!/usr/bin/env sed -f 
s/a/b/

Script akan gagal dieksekusi:

/usr/bin/env: sed -f: No such file or directory

Saya agak percaya bahwa itu terkait dengan -f. Tetapi bagaimana cara mengatasinya?

Cheng
sumber
stackoverflow.com/questions/4303128/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Jawaban:

33

Anda tidak bisa, dengan mudah, menempatkan lebih dari satu argumen pada satu #!baris . Itu berarti hanya path lengkap dan satu argumen (mis#!/bin/sed -f atau #!/usr/bin/sed -f), atau #!/usr/bin/envdan tidak ada argumen untuk penerjemah.

Sebuah solusi untuk mendapatkan skrip portabel adalah dengan menggunakan #!/bin/shdan pembungkus shell, melewati skrip sed sebagai argumen baris perintah. Perhatikan bahwa ini tidak disetujui oleh POSIX (skrip multi-instruksi harus ditulis dengan -eargumen terpisah untuk setiap instruksi untuk portabilitas), tetapi ia bekerja dengan banyak implementasi.

#!/bin/sh
exec sed '
s/a/b/
' "$@"

Untuk skrip yang panjang, mungkin lebih nyaman menggunakan heredoc. Keuntungan dari heredoc adalah bahwa Anda tidak perlu mengutip kutipan tunggal di dalam, jika ada. Kelemahan utama adalah bahwa skrip diumpankan ke input standarnya, dengan dua konsekuensi yang mengganggu. Beberapa versi sed membutuhkan -f /dev/stdinbukan -f -, yang merupakan masalah untuk portabilitas. Lebih buruk lagi, skrip tidak dapat bertindak sebagai filter, karena input standar adalah skrip dan tidak dapat berupa data.

#!/bin/sh
exec sed -f - -- "$@" <<'EOF'
s/a/b/
EOF

Kelemahan dari heredoc dapat diatasi dengan penggunaan yang bermanfaat cat. Karena ini menempatkan seluruh skrip pada baris perintah lagi, itu bukan POSIX-compliant, tetapi sebagian besar portabel dalam praktek.

#!/bin/sh
exec sed "$(cat <<'EOF')" -- "$@"
s/a/b/
EOF

Solusi lain adalah dengan menulis skrip yang dapat diuraikan baik oleh sh dan oleh sed. Ini portabel, cukup efisien, hanya sedikit jelek.

#! /bin/sh
b ()
{
x
}
i\
f true; then exec sed -f "$0" "$@"; fi
: ()
# sed script starts here
s/a/b/

Penjelasan:

  • Di bawah sh: tentukan fungsi yang disebut b ; isinya tidak masalah asalkan fungsi tersebut terbentuk dengan baik secara sintaksis (khususnya, Anda tidak dapat memiliki fungsi kosong). Kemudian jika benar (yaitu selalu), jalankan sedpada script.
  • Di bawah sed: bercabang ke ()label, kemudian beberapa input yang terbentuk dengan baik. Lalu sebuahi perintah, yang tidak memiliki efek karena selalu dilewati. Akhirnya ()label diikuti oleh bagian skrip yang bermanfaat.
  • Diuji di bawah GNU, BusyBox, dan OpenBSD. (Anda bisa lolos dengan sesuatu yang lebih sederhana pada GNU sed, tetapi OpenBSD sed pilih-pilih tentang bagian-bagian yang dilompati.)
Gilles 'SANGAT berhenti menjadi jahat'
sumber
1
"atau #!/usr/bin/envdan tidak ada argumen." tidak diungkapkan dengan sangat baik. Mungkin "atau #!/usr/bin/env seddan tidak ada argumen untuk sed."
cjm
+1 untuk "sedikit jelek" dan skrip dua bahasa :)
retracile
6

Ada berbagai implementasi yang tidak kompatibel dari shebang (#!) Tergantung pada OS. Ada yang membangun daftar argumen lengkap, ada yang mempertahankan jalur perintah dan menempatkan semua argumen yang tersisa sebagai satu, ada yang mengabaikan semua argumen dan hanya melewati jalur perintah, dan akhirnya, beberapa melewati seluruh string sebagai satu perintah. Anda tampaknya berada dalam kasus terakhir.

Jlliagre
sumber
2

env sedang mencoba menemukan file dengan nama "sed -f". Anda dapat mencoba "#! / Usr / bin / sed -f" sebagai baris shebang Anda.

anilmwr
sumber
2

Pada GNU coreutils v8.30 , Anda dapat melakukan:

#!/usr/bin/env -S sed -f

Fitur ini telah ditambahkan dalam baru-baru ini (2018/04/20) berkomitmen untuk env.cdi GNU coreutils paket, yang menambahkan -Satau --split-stringpilihan.

Dari envhalaman manual:

OPTIONS
-S/--split-string usage in scripts
    The  -S  option allows specifing multiple parameters in a script.
    Running a script named 1.pl containing the following first line:

            #!/usr/bin/env -S perl -w -T

    Will execute perl -w -T 1.pl .

    Without the '-S' parameter the script will likely fail with:

            /usr/bin/env: 'perl -w -T': No such file or directory

    See the full documentation for more details.

Lebih banyak contoh tersedia di manual GNU coreutils .

Jika Anda juga menggunakan -v opsi untuk keluaran verbose, Anda dapat melihat dengan tepat bagaimana cara envmembagi string argumen:

Dalam my_sed_script.sed:

#!/usr/bin/env -vS sed -f
s/a/b/

Eksekusi:

$ ./my_sed_script.sed
split -S:  ‘sed -f’
 into:    ‘sed’
     &    ‘-f’
executing: sed
   arg[0]= ‘sed’
   arg[1]= ‘-f’
   arg[2]= ‘./my_sed_script.sed’

Catatan: Ini hanya berlaku untuk shebang yang menggunakan /usr/bin/env, seperti --split-stringjuga fitur GNU env.

Amin Mesbah
sumber
1

Jawaban ini memberikan jalan ke solusi yang elegan: /programming//a/1655389/642372

  1. read a heredoc menjadi variabel shell.
  2. Lewati variabel itu sebagai argumen posisi sed.

Mencicipi:

#!/usr/bin/env bash

read -rd '' SED_SCRIPT <<EOD      
# Your sed script goes here.
s/a/b/
EOD

exec sed "$SED_SCRIPT" "$@"
Walker Hale IV
sumber
1

Saya suka solusi Walker, tetapi bisa diperbaiki (meletakkan ini di jawaban yang terpisah karena komentar tidak menerima teks yang telah diformat sebelumnya). Ini mungkin tidak berfungsi pada semua versi Linux atau Bash, tetapi pada Ubuntu 17.10 Anda dapat memotong ini sebagai berikut:

#!/usr/bin/env bash
read SED_SCRIPT <<EOD
s/a/b/
EOD
sed "$SED_SCRIPT" "$@"

Anda dapat menghapus evaldan menyederhanakan readperintah, tetapi Anda harus menyingkirkan komentar di dalam heredoc.

Juga, untuk semua yang ingin Anda ketahui tentang sed tetapi takut untuk bertanya, ada tutorial yang sangat berguna di http://www.grymoire.com/unix/sed.html . Ini menyarankan solusi yang lebih baik, dengan mengorbankan kehilangan /usr/bin/envdan mengkodekan jalur ke sed:

#!/bin/sed -f
s/a/b/
s/c/d/

Ini memiliki keuntungan tambahan untuk mendukung lebih dari satu s///penggantian.

Huw Walters
sumber