ฉันจะสร้างข้อโต้แย้งไปยังคำสั่งอื่นผ่านการทดแทนคำสั่งได้อย่างไร


11

ติดตามจาก: พฤติกรรมที่ไม่คาดคิดในการทดแทนคำสั่งเชลล์

ฉันมีคำสั่งที่สามารถใช้รายการของการขัดแย้งขนาดใหญ่ซึ่งบางอย่างสามารถมีช่องว่างที่ถูกต้องตามกฎหมาย (และอาจเป็นอย่างอื่น)

ฉันเขียนสคริปต์ที่สามารถสร้างข้อโต้แย้งเหล่านั้นสำหรับฉันด้วยเครื่องหมายคำพูด แต่ฉันต้องคัดลอกและวางผลลัพธ์เช่น

./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>

ฉันพยายามทำให้เพรียวบางนี้โดยทำ

./othercommand $(./somecommand)

และพบพฤติกรรมที่ไม่คาดคิดตามที่กล่าวถึงข้างต้น คำถามคือ - สามารถใช้การทดแทนคำสั่งได้อย่างน่าเชื่อถือในการสร้างอาร์กิวเมนต์เพื่อothercommandให้ข้อโต้แย้งบางอย่างจำเป็นต้องมีการเสนอราคาและสิ่งนี้ไม่สามารถเปลี่ยนแปลงได้?


ขึ้นอยู่กับสิ่งที่คุณหมายถึงโดย "เชื่อถือได้" หากคุณต้องการคำสั่งถัดไปใช้เอาท์พุทตรงตามที่ปรากฏบนหน้าจอและใช้กฎของเชลล์กับมันก็อาจจะevalสามารถใช้งานได้ แต่โดยทั่วไปจะไม่แนะนำ xargsเป็นสิ่งที่ต้องพิจารณาเช่นกัน
Sergiy Kolodyazhnyy

ฉันต้องการ (คาดหวัง) ผลลัพธ์จากsomecommandการผ่านการแยกวิเคราะห์เชลล์ปกติ
user1207217

ที่ผมกล่าวในคำตอบของฉันใช้บางส่วนของตัวละครอื่น ๆ สำหรับการแยกข้อมูล (ชอบ:) ... สมมติว่าตัวละครที่จะเชื่อถือไม่ได้อยู่ในการส่งออก
Olorin

แต่นั่นไม่ถูกต้องเพราะไม่ปฏิบัติตามกฎการอ้างอิงซึ่งเป็นสิ่งที่เกี่ยวกับ
user1207217

2
คุณสามารถโพสต์ตัวอย่างจริงได้ไหม ฉันหมายความว่าเอาต์พุตจริงของคำสั่งแรกและวิธีที่คุณต้องการให้โต้ตอบกับคำสั่งที่สอง
nxnev

คำตอบ:


10

ฉันเขียนสคริปต์ที่สามารถสร้างข้อโต้แย้งเหล่านั้นสำหรับฉันด้วยคำพูด

หากมีการเสนอเอาท์พุทของเชลล์อย่างเหมาะสมและคุณเชื่อใจในเอาต์พุตคุณก็สามารถรันevalมันได้

สมมติว่าคุณมีเชลล์ที่สนับสนุนอาร์เรย์คุณควรใช้เชลล์เพื่อเก็บอาร์กิวเมนต์ที่คุณได้รับ

หาก./gen_args.shผลิตออกมาเช่น'foo bar' '*' asdfนั้นเราสามารถเรียกใช้eval "args=( $(./gen_args.sh) )"เพื่อเติมอาร์เรย์ที่เรียกว่าargsมีผล ที่จะเป็นสามองค์ประกอบfoo bar, ,*asdf

เราสามารถใช้"${args[@]}"ตามปกติเพื่อขยายองค์ประกอบอาร์เรย์เป็นรายบุคคล:

$ eval "args=( $(./gen_args.sh) )"
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

(สังเกตเครื่องหมายคำพูด. "${array[@]}"ขยายไปยังองค์ประกอบทั้งหมดเป็นอาร์กิวเมนต์ที่แตกต่างที่ไม่ได้แก้ไขโดยไม่ต้องใส่เครื่องหมายอัญประกาศองค์ประกอบอาเรย์อาจมีการแบ่งคำดูเช่นหน้า Arays ใน BashGuide )

อย่างไรก็ตาม , evalมีความสุขจะทำงานแทนเปลือกใด ๆ ดังนั้นในการส่งออกจะขยายตัวไปยังไดเรกทอรีที่บ้านของคุณและแทนคำสั่งจริงจะเรียกใช้คำสั่งในเปลือกทำงาน$HOME evalผลลัพธ์ของ"$(date >&2)"จะสร้างองค์ประกอบอาเรย์ที่ว่างเปล่าหนึ่งชิ้นและพิมพ์วันที่ปัจจุบันบน stdout นี่เป็นข้อกังวลหากgen_args.shได้รับข้อมูลจากแหล่งที่ไม่น่าเชื่อถือเช่นโฮสต์อื่นผ่านเครือข่ายชื่อไฟล์ที่สร้างโดยผู้ใช้รายอื่น ผลลัพธ์อาจรวมถึงคำสั่งโดยพลการ (หากget_args.shตัวเองเป็นอันตรายก็ไม่จำเป็นต้องส่งออกอะไรก็สามารถเรียกใช้คำสั่งที่เป็นอันตรายโดยตรง)


อีกทางเลือกหนึ่งในการอ้างอิงเชลล์ซึ่งเป็นการยากที่จะแยกวิเคราะห์โดยไม่ต้องใช้ eval คือการใช้อักขระอื่น ๆ เป็นตัวคั่นในเอาต์พุตของสคริปต์ของคุณ คุณจะต้องเลือกอย่างใดอย่างหนึ่งที่ไม่จำเป็นในข้อโต้แย้งที่เกิดขึ้นจริง

ลองเลือกและมีการส่งออกสคริปต์# foo bar#*#asdfตอนนี้เราสามารถใช้unquotedการขยายตัวของคำสั่งที่จะแยกผลลัพธ์ของคำสั่งที่จะโต้แย้ง

$ IFS='#'                          # split on '#' signs
$ set -f                           # disable globbing
$ args=( $( ./gen_args3.sh ) )     # assign the values to the arrayfor var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

คุณจะต้องIFSกลับมาใหม่ในภายหลังหากคุณขึ้นอยู่กับการแยกคำที่อื่นในสคริปต์ ( unset IFSควรใช้เพื่อทำให้เป็นค่าเริ่มต้น) และใช้set +fถ้าคุณต้องการใช้การแปลในภายหลัง

หากคุณไม่ได้ใช้ Bash หรือเชลล์อื่น ๆ ที่มีอาร์เรย์คุณสามารถใช้พารามิเตอร์ตำแหน่งสำหรับสิ่งนั้นได้ แทนที่args=( $(...) )ด้วยset -- $(./gen_args.sh)และใช้"$@"แทน"${args[@]}"แล้ว (ที่นี่เช่นกันคุณต้องใส่เครื่องหมายอัญประกาศ"$@"มิฉะนั้นพารามิเตอร์ตำแหน่งจะขึ้นอยู่กับการแยกคำ)


สุดยอดของทั้งสองโลก!
Olorin

คุณช่วยเพิ่มคำพูดที่แสดงถึงความสำคัญของการอ้างอิง${args[@]}ได้หรือไม่ - นี่ไม่ได้ผลสำหรับฉัน
user1207217

@ user1207217 ใช่คุณพูดถูก มันเป็นสิ่งเดียวกันกับอาร์เรย์และเช่นเดียวกับ"${array[@]}" "$@"ทั้งสองจะต้องมีการเสนอราคาหรือมิฉะนั้นการแยกคำแบ่งองค์ประกอบอาร์เรย์ไปยังส่วน
ilkkachu

6

ปัญหาคือเมื่อsomecommandสคริปต์ของคุณออกตัวเลือกสำหรับothercommandตัวเลือกที่เป็นจริงเพียงข้อความและความเมตตาของเชลล์แยกมาตรฐาน (ได้รับผลกระทบจากสิ่งที่$IFSเกิดขึ้นและสิ่งที่ตัวเลือกเชลล์มีผลซึ่งในกรณีทั่วไปจะไม่อยู่ในการควบคุม)

แทนการใช้somecommandเพื่อการส่งออกตัวเลือกมันจะง่ายขึ้นปลอดภัยขึ้นและมีประสิทธิภาพมากขึ้นที่จะใช้เพื่อการโทร สคริปต์แล้วจะเป็นสคริปต์เสื้อคลุมรอบแทนการเรียงลำดับของสคริปต์ผู้ช่วยบางอย่างที่คุณจะต้องจำไว้ว่าให้เรียกในลักษณะพิเศษบางอย่างที่เป็นส่วนหนึ่งของบรรทัดคำสั่งของ Wrapper สคริปต์เป็นวิธีที่ใช้กันมากในการให้เครื่องมือที่เรียกเพียงบางส่วนเครื่องมือที่คล้ายกันอื่น ๆ ที่มีชุดของตัวเลือกอื่น (เพียงตรวจสอบกับสิ่งที่คำสั่งในมีห่อเชลล์สคริปต์จริง)othercommandsomecommandothercommandotherscriptfile/usr/bin

ในbash, kshหรือzshคุณสามารถได้อย่างง่ายดายสคริปต์เสื้อคลุมที่ใช้อาร์เรย์จะถือตัวเลือกสำหรับแต่ละothercommandชอบโดย:

options=( "hi there" "nice weather" "here's a star" "*" )
options+=( "bonus bumblebee!" )  # add additional option

จากนั้นโทรothercommand(ยังอยู่ในสคริปต์ตัวตัดคำ):

othercommand "${options[@]}"

การขยายตัวของ"${options[@]}"จะให้แน่ใจว่าแต่ละองค์ประกอบของoptionsอาร์เรย์จะถูกยกมาเป็นรายบุคคลและนำเสนอให้othercommandเป็นอาร์กิวเมนต์ที่แยกต่างหาก

ผู้ใช้ wrapper นั้นจะไม่สนใจความจริงที่ว่ามันกำลังเรียกothercommandสิ่งที่ไม่เป็นความจริงถ้าสคริปต์สร้างตัวเลือกบรรทัดคำสั่งแทนothercommandเอาท์พุทแทน

ใน/bin/shใช้$@เพื่อเก็บตัวเลือก:

set -- "hi there" "nice weather" "here's a star" "*"
set -- "$@" "bonus bumblebee!"  # add additional option

othercommand "$@"

( setเป็นคำสั่งที่ใช้สำหรับการตั้งค่าพารามิเตอร์ตำแหน่ง$1, $2, $3ฯลฯ เหล่านี้เป็นสิ่งที่ทำให้ขึ้นอาร์เรย์$@ในมาตรฐาน POSIX เปลือก. เริ่มต้น--คือการส่งสัญญาณไปยังsetว่ามีตัวเลือกไม่รับข้อโต้แย้งเพียง. --ถูกจริงๆจำเป็นเฉพาะในกรณีที่ ค่าแรกเกิดขึ้นกับสิ่งที่เริ่มต้นด้วย-)

โปรดทราบว่ามันเป็นเครื่องหมายคำพูดคู่รอบ ๆ$@และ${options[@]}เพื่อให้แน่ใจว่าองค์ประกอบจะไม่แยกคำแต่ละคำ (และชื่อไฟล์กลม)


คุณอธิบายได้set --ไหม
user1207217

@ user1207217 เพิ่มคำอธิบายเพื่อตอบ
Kusalananda

4

หากsomecommandเอาต์พุตอยู่ในรูปแบบเชลล์ที่ดีเชื่อถือได้คุณสามารถใช้eval:

$ eval sh test.sh $(echo '"hello " "hi and bye"')
hello 
hi and bye

แต่คุณต้องแน่ใจว่าเอาต์พุตมีการอ้างอิงที่ถูกต้องและไม่เช่นนั้นคุณอาจสิ้นสุดการรันคำสั่งนอกสคริปต์เช่นกัน:

$ cat test.sh 
for var in "$@"
do
    echo "|$var|"
done
$ ls
bar  baz  test.sh
$ eval sh test.sh $(echo '"hello " "hi and bye"; echo rm *')
|hello |
|hi and bye|
rm bar baz test.sh

โปรดทราบว่าไม่ได้echo rm bar baz test.shถูกส่งไปยังสคริปต์ (เนื่องจาก;) และถูกเรียกใช้เป็นคำสั่งแยกต่างหาก ฉันเพิ่ม|รอบ$varเพื่อเน้นนี้


โดยทั่วไปถ้าคุณไม่สามารถเชื่อถือได้อย่างสมบูรณ์เอาท์พุทของsomecommandมันเป็นไปไม่ได้ที่จะใช้เอาท์พุทได้อย่างน่าเชื่อถือในการสร้างสตริงคำสั่ง

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