คำตอบเหล่านี้ส่วนใหญ่เกิดขึ้นกับกรณีเฉพาะที่คุณถาม มีวิธีการทั่วไปที่เพื่อนและผมได้พัฒนาที่ช่วยให้การโดยพลการอ้างในกรณีที่คุณจำเป็นต้องอ้างทุบตีคำสั่งผ่านหลายชั้นของการขยายตัวของเปลือกเช่นผ่าน SSH, su -c
, bash -c
ฯลฯ มีเป็นหนึ่งในหลักดั้งเดิมที่คุณต้องการที่นี่ ในทุบตีพื้นเมือง:
quote_args() {
local sq="'"
local dq='"'
local space=""
local arg
for arg; do
echo -n "$space'${arg//$sq/$sq$dq$sq$dq$sq}'"
space=" "
done
}
สิ่งนี้ทำในสิ่งที่มันบอก: มัน shell-อัญประกาศแต่ละอาร์กิวเมนต์เป็นรายบุคคล (หลังจากการขยาย bash, แน่นอน):
$ quote_args foo bar
'foo' 'bar'
$ quote_args arg1 'arg2 arg2a' arg3
'arg1' 'arg2 arg2a' 'arg3'
$ quote_args dq'"'
'dq"'
$ quote_args dq'"' sq"'"
'dq"' 'sq'"'"''
$ quote_args "*"
'*'
$ quote_args /b*
'/bin' '/boot'
มันเป็นสิ่งที่ชัดเจนสำหรับการขยายชั้นหนึ่ง:
$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2
(โปรดทราบว่า$(quote_args ...)
จำเป็นต้องใช้เครื่องหมายอัญประกาศคู่ล้อมรอบเพื่อสร้างผลลัพธ์ให้เป็นอาร์กิวเมนต์เดียวจนถึงbash -c
) และสามารถนำมาใช้เพื่ออ้างอิงอย่างถูกต้องผ่านการขยายหลายชั้น:
$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2
ตัวอย่างข้างต้น:
- shell-quote แต่ละอาร์กิวเมนต์ไปยังด้านใน
quote_args
ทีละรายการจากนั้นรวมผลลัพธ์ที่ได้ลงในอาร์กิวเมนต์เดียวกับเครื่องหมายคำพูดคู่ด้านใน
- เปลือกคำพูด
bash
, -c
และผลแล้วครั้งหนึ่งที่ยกมาจากขั้นตอนที่ 1 แล้วรวมผลเป็นอาร์กิวเมนต์เดียวกับคำพูดสองด้านนอก
bash -c
ส่งระเบียบที่เป็นอาร์กิวเมนต์ไปด้านนอก
นั่นเป็นความคิดที่สั้น คุณสามารถทำสิ่งที่ค่อนข้างซับซ้อนได้ด้วยสิ่งนี้ แต่คุณต้องระวังเกี่ยวกับลำดับของการประเมินและเกี่ยวกับสารตั้งต้นที่ยกมา ตัวอย่างเช่นต่อไปนี้ทำสิ่งที่ผิด (สำหรับคำนิยามของ "ผิด" บางอย่าง):
$ (cd /tmp; bash -c "$(quote_args cd /; pwd 1>&2)")
/tmp
$ (cd /tmp; bash -c "$(quote_args cd /; [ -e *sbin ] && echo success 1>&2 || echo failure 1>&2)")
failure
ในตัวอย่างแรกทุบตีทันทีขยายquote_args cd /; pwd 1>&2
เป็นสองคำสั่งที่แยกจากกันquote_args cd /
และpwd 1>&2
ดังนั้น CWD ยังคงเป็น/tmp
เมื่อpwd
คำสั่งจะถูกดำเนินการ ตัวอย่างที่สองแสดงให้เห็นถึงปัญหาที่คล้ายกันสำหรับ globbing แท้จริงแล้วปัญหาพื้นฐานเดียวกันนี้เกิดขึ้นกับการขยาย bash ทั้งหมด ปัญหาที่นี่คือการทดแทนคำสั่งไม่ใช่การเรียกใช้ฟังก์ชัน: เป็นการประเมินสคริปต์ bash หนึ่งตัวและใช้เอาต์พุตเป็นส่วนหนึ่งของสคริปต์ bash อื่น
หากคุณพยายามที่จะหลบหนีตัวดำเนินการเชลล์คุณจะล้มเหลวเพราะสตริงผลลัพธ์ที่ส่งผ่านไปbash -c
เป็นเพียงลำดับของสตริงที่ยกมาเป็นรายบุคคลซึ่งจะไม่ถูกตีความว่าเป็นตัวดำเนินการซึ่งง่ายต่อการดูว่าคุณสะท้อนสตริงที่จะ ถูกส่งไปทุบตี:
$ (cd /tmp; echo "$(quote_args cd /\; pwd 1\>\&2)")
'cd' '/;' 'pwd' '1>&2'
$ (cd /tmp; echo "$(quote_args cd /\; \[ -e \*sbin \] \&\& echo success 1\>\&2 \|\| echo failure 1\>\&2)")
'cd' '/;' '[' '-e' '*sbin' ']' '&&' 'echo' 'success' '1>&2' '||' 'echo' 'failure' '1>&2'
ปัญหาที่นี่คือคุณกำลังอ้างถึงมากเกินไป สิ่งที่คุณต้องการสำหรับโอเปอเรเตอร์ที่จะไม่ต้องใส่เครื่องหมายคำพูดเป็นอินพุตของการปิดล้อมbash -c
ซึ่งหมายความว่าพวกเขาจำเป็นต้องอยู่นอกการ$(quote_args ...)
ทดแทนคำสั่ง
ดังนั้นสิ่งที่คุณต้องทำโดยทั่วไปคือเชลล์คำพูดแต่ละคำของคำสั่งที่ไม่ได้มีวัตถุประสงค์เพื่อขยายเวลาการทดแทนคำสั่งแยกต่างหากและไม่ใช้การอ้างถึงพิเศษใด ๆ กับตัวดำเนินการเชลล์:
$ (cd /tmp; echo "$(quote_args cd /); $(quote_args pwd) 1>&2")
'cd' '/'; 'pwd' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")
/
$ (cd /tmp; echo "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
'cd' '/'; [ -e *'sbin' ] && 'echo' 'success' 1>&2 || 'echo' 'failure' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
success
เมื่อคุณดำเนินการเสร็จแล้วสตริงทั้งหมดจะเป็นเกมที่ยุติธรรมสำหรับการอ้างอิงระดับการประเมินตามอำเภอใจต่อไป:
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")"
/
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")"
/
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")")"
/
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")"
success
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *sbin ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")"
success
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")")"
success
เป็นต้น
ตัวอย่างเหล่านี้อาจดูเหมือนให้ประณีตว่าคำพูดชอบsuccess
, sbin
และpwd
ไม่จำเป็นต้องเป็นเปลือกที่ยกมา แต่จุดสำคัญที่ต้องจำไว้เมื่อเขียนสคริปต์การป้อนข้อมูลโดยพลการคือการที่คุณต้องการที่จะพูดทุกอย่างที่คุณไม่แน่ใจว่าอย่างdoesn' ทีRobert'; rm -rf /
จำเป็นต้องอ้างเพราะคุณไม่เคยรู้ว่าเมื่อผู้ใช้จะโยนใน
เพื่อให้เข้าใจสิ่งที่เกิดขึ้นภายใต้หน้ากากได้ดีขึ้นคุณสามารถเล่นกับฟังก์ชั่นตัวช่วยขนาดเล็กสองแบบ:
debug_args() {
for (( I=1; $I <= $#; I++ )); do
echo -n "$I:<${!I}> " 1>&2
done
echo 1>&2
}
debug_args_and_run() {
debug_args "$@"
"$@"
}
ที่จะแจกแจงแต่ละอาร์กิวเมนต์ให้กับคำสั่งก่อนที่จะดำเนินการ:
$ debug_args_and_run echo a'"'b"'"c arg2
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)"
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'bash'"'"' '"'"'-c'"'"' '"'"''"'"'"'"'"'"'"'"'debug_args_and_run'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'echo'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'a"b'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'c'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'arg2'"'"'"'"'"'"'"'"''"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2