วิธีใช้ `/ usr / bin / env sed -f 'ใน shebang?


25

การพิมพ์/usr/bin/env sed -fงานเทอร์มินัล

แต่ถ้าใช้เป็น Shebang

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

สคริปต์จะล้มเหลวในการดำเนินการ:

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

ฉันเชื่อว่ามันเกี่ยวข้องกับ -f แต่จะแก้ไขได้อย่างไร?


คำตอบ:


33

คุณไม่สามารถพกพาวางอาร์กิวเมนต์มากกว่าหนึ่งตัวในหนึ่ง#!บรรทัดได้ นั่นหมายถึงเส้นทางแบบเต็มและอาร์กิวเมนต์เดียว (เช่น#!/bin/sed -fหรือ#!/usr/bin/sed -f) หรือ#!/usr/bin/envไม่มีอาร์กิวเมนต์สำหรับผู้แปล

วิธีหลีกเลี่ยงปัญหาในการรับสคริปต์แบบพกพาคือการใช้#!/bin/shและ shell wrapper โดยส่งผ่านสคริปต์ sed เป็นอาร์กิวเมนต์บรรทัดคำสั่ง โปรดทราบว่านี่ไม่ใช่ทำนองคลองธรรมโดย POSIX (สคริปต์หลายคำสั่งจะต้องเขียนด้วย-eอาร์กิวเมนต์แยกต่างหากสำหรับแต่ละคำสั่งเพื่อความสะดวกในการพกพา) แต่มันทำงานได้กับการใช้งานหลายอย่าง

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

สำหรับสคริปต์ที่ยาวอาจจะสะดวกกว่าถ้าใช้ heredoc ข้อดีของ heredoc คือคุณไม่จำเป็นต้องพูดคำพูดเดียวภายในถ้ามี ข้อเสียที่สำคัญคือสคริปต์ถูกป้อนเข้ากับมาตรฐานอินพุตพร้อมกับผลที่น่ารำคาญสองประการ sed บางรุ่นต้องการ-f /dev/stdinแทน-f -ซึ่งเป็นปัญหาสำหรับการพกพา ยิ่งไปกว่านั้นสคริปต์ไม่สามารถทำหน้าที่เป็นตัวกรองได้เนื่องจากอินพุตมาตรฐานคือสคริปต์และไม่สามารถเป็นข้อมูลได้

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

ข้อเสียของการ heredoc catสามารถแก้ไขได้โดยการใช้งานของ เนื่องจากสิ่งนี้ทำให้สคริปต์ทั้งหมดบนบรรทัดคำสั่งอีกครั้งจึงไม่สอดคล้องกับ POSIX แต่ส่วนใหญ่พกพาได้ในทางปฏิบัติ

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

วิธีแก้ปัญหาอื่นคือการเขียนสคริปต์ที่สามารถแยกวิเคราะห์ทั้งโดย sh และโดย sed นี้เป็นแบบพกพาที่มีประสิทธิภาพพอสมควรเพียงเล็กน้อยที่น่าเกลียด

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

คำอธิบาย:

  • ภายใต้ sh: กำหนดฟังก์ชั่นที่เรียกว่าb; เนื้อหาไม่สำคัญตราบเท่าที่ฟังก์ชั่นนั้นมีรูปแบบที่ดี (โดยเฉพาะคุณไม่สามารถมีฟังก์ชั่นที่ว่างเปล่า) ถ้าเป็นจริงให้ดำเนินการsedกับสคริปต์
  • ภายใต้ sed: แยกสาขาไปที่()ป้ายกำกับ จากนั้นiคำสั่งซึ่งไม่มีผลกระทบเพราะถูกข้ามไปเสมอ ในที่สุด()ฉลากตามด้วยส่วนที่มีประโยชน์ของสคริปต์
  • ทดสอบภายใต้ GNU sed, BusyBox และ OpenBSD (คุณสามารถออกไปกับสิ่งที่ง่ายกว่าใน GNU sed แต่ OpenBSD sed เป็นเรื่องเกี่ยวกับชิ้นส่วนที่ข้ามไป)

1
"หรือ#!/usr/bin/envไม่โต้แย้ง" ประโยคไม่ดีมาก อาจจะ "หรือ#!/usr/bin/env sedไม่ก็โต้เถียงกันก็ได้"
cjm

+1 สำหรับ "เพียงเล็กน้อยน่าเกลียด" และสคริปต์แบบ dual-ภาษา :)
retracile

6

มีการใช้งานที่ไม่เข้ากันของ shebang (#!) ขึ้นอยู่กับระบบปฏิบัติการ บางรายการกำลังสร้างรายการอาร์กิวเมนต์แบบเต็มบางรายการกำลังเก็บรักษาพา ธ คำสั่งและใส่อาร์กิวเมนต์ที่เหลือทั้งหมดเป็นรายการเดียวบางรายการจะละเว้นอาร์กิวเมนต์ทั้งหมดและส่งผ่านเฉพาะเส้นทางคำสั่งเท่านั้นและสุดท้ายบางรายการจะผ่านสตริงทั้งหมดเป็นรายการเดียว คำสั่ง คุณดูเหมือนจะเป็นในกรณีหลัง


2

env พยายามค้นหาไฟล์ที่มีชื่อ "sed -f" คุณสามารถลอง "#! / usr / bin / sed -f" เป็นบรรทัด shebang ของคุณ


2

ในฐานะของ GNU coreutils v8.30คุณสามารถทำได้:

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

คุณลักษณะนี้ถูกเพิ่มเข้ามาในเร็ว ๆ นี้ (2018/04/20) กระทำการenv.cในแพคเกจ GNU coreutils ซึ่งเพิ่ม-Sหรือ--split-stringตัวเลือก

จากenvหน้าคน:

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.

ตัวอย่างอื่น ๆ ที่มีอยู่ในGNU coreutils คู่มือ

หากคุณใช้-vตัวเลือกสำหรับเอาต์พุต verbose คุณสามารถดูวิธี envแยกสตริงของอาร์กิวเมนต์:

ในmy_sed_script.sed:

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

การดำเนินการ:

$ ./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’

หมายเหตุ:สิ่งนี้ใช้กับ shebangs ที่ใช้/usr/bin/envเท่านั้นเนื่องจาก --split-stringเป็นคุณลักษณะของ GNU envโดยเฉพาะ


1

คำตอบนี้ให้เส้นทางสู่โซลูชันที่หรูหรา: /programming//a/1655389/642372

  1. read heredoc เป็นตัวแปรเชลล์
  2. sedผ่านตัวแปรที่เป็นอาร์กิวเมนต์ตำแหน่งไป

ตัวอย่าง:

#!/usr/bin/env bash

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

exec sed "$SED_SCRIPT" "$@"

1

ฉันชอบโซลูชันของวอล์คเกอร์ แต่สามารถปรับปรุงได้ (ควรใส่คำตอบแยกต่างหากเนื่องจากความคิดเห็นไม่ยอมรับข้อความที่ฟอร์แมตแล้ว) สิ่งนี้อาจใช้ไม่ได้กับ Linux หรือ Bash ทุกรุ่น แต่บน Ubuntu 17.10 คุณสามารถลดสิ่งต่อไปนี้ได้:

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

คุณสามารถลบevalและทำให้readคำสั่งง่ายขึ้นแต่คุณต้องกำจัดความคิดเห็นที่อยู่ใน heredoc

นอกจากนี้สำหรับทุกสิ่งที่คุณอยากรู้เกี่ยวกับ sed แต่ก็กลัวที่จะถามมีการกวดวิชาที่มีประโยชน์มากที่http://www.grymoire.com/unix/sed.html สิ่งนี้ชี้ให้เห็นทางออกที่ดียิ่งขึ้นโดยเสียค่าใช้จ่ายในการสูญเสีย/usr/bin/envและเข้ารหัสฮาร์ดไดรฟ์ไปยัง:

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

นี่เป็นข้อดีเพิ่มเติมของการสนับสนุนการแทนที่มากกว่าหนึ่งs///รายการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.