หมายเหตุ: @ jw013 ทำให้การคัดค้านที่ไม่ได้รับการสนับสนุนดังต่อไปนี้ในความคิดเห็นด้านล่าง:
Downvote นั้นเป็นเพราะรหัสการแก้ไขตัวเองนั้นโดยทั่วไปถือว่าเป็นการปฏิบัติที่ไม่ดี ย้อนกลับไปในสมัยก่อนของโปรแกรมการประกอบเล็ก ๆ มันเป็นวิธีที่ชาญฉลาดในการลดสาขาที่มีเงื่อนไขและปรับปรุงประสิทธิภาพ แต่ปัจจุบันความเสี่ยงด้านความปลอดภัยมีมากกว่าข้อดี วิธีการของคุณจะไม่ทำงานหากผู้ใช้ที่เรียกใช้สคริปต์ไม่มีสิทธิ์ในการเขียนสคริปต์
ฉันตอบคัดค้านการรักษาความปลอดภัยของเขาโดยชี้ให้เห็นว่าการใด ๆสิทธิ์พิเศษที่จำเป็นต้องใช้เพียงครั้งเดียวต่อการติดตั้ง / การปรับปรุงการดำเนินการเพื่อที่จะติดตั้ง / อัปเดตตนเองติดตั้งสคริปต์ - ซึ่งผมเองจะเรียกที่เชื่อถือได้สวย ฉันยังชี้ให้เขาman sh
อ้างอิงถึงการบรรลุเป้าหมายที่คล้ายกันด้วยวิธีการที่คล้ายกัน ในเวลานั้นฉันไม่ได้สนใจที่จะชี้ให้เห็นว่าข้อบกพร่องด้านความปลอดภัยหรือการปฏิบัติที่ไม่สมควรซึ่งอาจจะใช่หรือไม่ใช่ในคำตอบของฉันพวกเขามีแนวโน้มที่จะฝังรากอยู่ในคำถามมากกว่าคำตอบของฉัน:
ฉันจะตั้งค่า shebang เพื่อให้เรียกใช้สคริปต์เป็น /path/to/script.sh จะใช้ Zsh ใน PATH ได้อย่างไร
ไม่พอใจ @ jw013 ยังคงคัดค้านโดยการโต้แย้งว่าเขายังไม่ได้รับการสนับสนุนโดยมีอย่างน้อยสองประโยคที่ผิดพลาด:
คุณใช้ไฟล์เดียวไม่ใช่สองไฟล์ [ man sh
อ้างอิง]
แพคเกจได้หนึ่งไฟล์แก้ไขไฟล์อื่น คุณมีไฟล์ที่แก้ไขเอง มีความแตกต่างที่ชัดเจนระหว่างสองกรณีนี้ ไฟล์ที่รับอินพุตและสร้างเอาต์พุตนั้นใช้ได้ ไฟล์เรียกทำงานที่เปลี่ยนแปลงตัวเองในขณะที่ทำงานนั้นโดยทั่วไปจะเป็นความคิดที่ไม่ดี ตัวอย่างที่คุณชี้ไปไม่ได้ทำเช่นนั้น
ในที่แรก:
รหัสEXECUTABLEเดียวในสคริปต์EXECUTABLE SHELL ใด ๆ ก็คือ#!
ITSELF
(แม้ว่า#!
จะไม่ได้ระบุอย่างเป็นทางการ )
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
{ ${l=lsof -p} $$
echo "$l \$$" | sh
} | grep \
"COMMAND\|^..*sh\| [0-9]*[wru] "
#END
FILE
##OUTPUT
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
file 8900 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
file 8900 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
file 8900 mikeserv 0r REG 0,35 108 15496912 /tmp/zshUTTARQ (deleted)
file 8900 mikeserv 1u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 255r REG 0,33 108 2134129 /home/mikeserv/file
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sh 8906 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
sh 8906 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
sh 8906 mikeserv 0r FIFO 0,8 0t0 15500515 pipe
sh 8906 mikeserv 1w FIFO 0,8 0t0 15500514 pipe
sh 8906 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
{ sed -i \
'1c#!/home/mikeserv/file' ./file
./file
sh -c './file ; echo'
grep '#!' ./file
}
##OUTPUT
zsh: too many levels of symbolic links: ./file
sh: ./file: /home/mikeserv/file: bad interpreter: Too many levels of symbolic links
#!/home/mikeserv/file
เชลล์สคริปต์เป็นเพียงแฟ้มข้อความ - เพื่อให้มันมีผลกระทบใด ๆ ที่ทุกคนจะต้องมีการอ่านจากแฟ้มที่ปฏิบัติการอีกคำแนะนำแล้วตีความโดยแฟ้มที่ปฏิบัติการอื่น ๆ ที่ก่อนที่จะแฟ้มที่ปฏิบัติการอื่น ๆ แล้วรันตีความของ เชลล์สคริปต์ เป็นไปไม่ได้ที่การเรียกใช้งานเชลล์สคริปต์ไฟล์จะเกี่ยวข้องกับไฟล์น้อยกว่าสองไฟล์ มีข้อยกเว้นที่เป็นไปได้ในzsh
คอมไพเลอร์ของตัวเอง แต่ด้วยสิ่งนี้ฉันมีประสบการณ์น้อยและมันไม่มีทางเป็นตัวแทนที่นี่
hashbang ของเชลล์สคริปต์จะต้องชี้ไปที่ล่ามที่ตั้งใจไว้หรือถูกลบทิ้งไปโดยไม่เกี่ยวข้อง
เชลล์มีสองโหมดพื้นฐานของการแยกวิเคราะห์และตีความอินพุต: ทั้งอินพุตปัจจุบันกำลังนิยาม a <<here_document
หรือนิยาม{ ( command |&&|| list ) ; } &
- ในคำอื่น ๆ เชลล์จะตีความโทเค็นเป็นตัวคั่นสำหรับคำสั่งที่ควรดำเนินการเมื่ออ่านแล้ว ในหรือเป็นคำแนะนำในการสร้างไฟล์และแมปไปยังไฟล์ descriptor สำหรับคำสั่งอื่น แค่นั้นแหละ.
เมื่อตีความคำสั่งเพื่อดำเนินการเชลล์จะ จำกัด โทเค็นในชุดของคำที่สงวนไว้ เมื่อเปลือกพบการเปิดโทเค็นก็จะต้องดำเนินการต่อไปอ่านในรายการคำสั่งจนกว่ารายการจะถูกคั่นด้วยทั้งโดยการปิดโทเค็นเช่นการขึ้นบรรทัดใหม่ได้ - เมื่อบังคับ - หรือปิด token เช่น})
สำหรับ({
ก่อนที่จะดำเนินการ
เชลล์แยกความแตกต่างระหว่างคำสั่งง่าย ๆและคำสั่งผสม คำสั่งผสมคือชุดของคำสั่งที่ต้องอ่านก่อนที่จะดำเนินการ แต่เปลือกไม่ได้ดำเนินการ$expansion
ใด ๆ ของส่วนประกอบของคำสั่งง่ายๆจนกว่าจะดำเนินการโดยลำพังแต่ละคน
ดังนั้นในตัวอย่างต่อไปนี้;semicolon
คำสงวนสงวนคั่นคำสั่งง่าย ๆแต่ละคำในขณะที่อักขระที่ไม่ใช้ Escape จะ\newline
คั่นระหว่างคำสั่งผสมสองคำ:
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
echo "simple command ${sc=1}" ;\
: > $0 ;\
echo "simple command $((sc+2))" ;\
sh -c "./file && echo hooray"
sh -c "./file && echo hooray"
#END
FILE
##OUTPUT
simple command 1
simple command 3
hooray
นั่นคือความเรียบง่ายของแนวทาง มันซับซ้อนมากขึ้นเมื่อคุณพิจารณาshell-buildins, subshells, สภาพแวดล้อมปัจจุบันและอื่น ๆ แต่สำหรับจุดประสงค์ของฉันที่นี่ก็เพียงพอแล้ว
และการพูดถึงบิวด์อินและรายการคำสั่ง a function() { declaration ; }
เป็นเพียงวิธีการกำหนดคำสั่งผสมให้กับคำสั่งง่าย ๆ เชลล์จะต้องไม่ดำเนินการใด ๆ$expansions
กับคำสั่งการประกาศตัวเอง - เพื่อรวม<<redirections>
- แต่จะต้องจัดเก็บคำจำกัดความเป็นสตริงตัวอักษรเดียวและดำเนินการมันเป็นเปลือกพิเศษในตัวเมื่อเรียกร้อง
ดังนั้นฟังก์ชั่นของเชลล์ที่ประกาศในเชลล์สคริปต์ที่รันได้จะถูกเก็บไว้ในหน่วยความจำของเชลล์ในรูปแบบสตริงตัวอักษรโดยไม่ขยายเพื่อรวมเอกสารต่อท้ายที่นี่เป็นอินพุตและดำเนินการอย่างอิสระจากไฟล์ต้นฉบับทุกครั้งที่มันเรียกว่า ในตราบเท่าที่สภาพแวดล้อมปัจจุบันของเชลล์คงอยู่
ตัวดำเนินการเปลี่ยนเส้นทาง<<
และ<<-
ทั้งคู่อนุญาตการเปลี่ยนเส้นทางของบรรทัดที่มีอยู่ในไฟล์อินพุตเชลล์หรือที่เรียกว่าhere-documentไปยังอินพุตของคำสั่ง
นี่เอกสารให้ถือเป็นคำเดียวว่าจะเริ่มต้นหลังจากต่อไป\newline
และต่อไปจนกว่าจะมีการโฆษณาที่มีเพียงตัวคั่นและ\newline
ไม่มี[:blank:]
ในระหว่าง จากนั้นเอกสารต่อไปนี้จะเริ่มขึ้นถ้ามี รูปแบบดังต่อไปนี้:
[n]<<word
here-document
delimiter
... โดยที่ทางเลือกn
แทนหมายเลขไฟล์ descriptor หากไม่ระบุหมายเลขเอกสารที่นี่จะอ้างถึงอินพุตมาตรฐาน (ตัวอธิบายไฟล์ 0)
for shell in dash zsh bash sh ; do sudo $shell -c '
{ readlink /proc/self/fd/3
cat <&3
} 3<<-FILE
$0
FILE
' ; done
#OUTPUT
pipe:[16582351]
dash
/tmp/zshqs0lKX (deleted)
zsh
/tmp/sh-thd-955082504 (deleted)
bash
/tmp/sh-thd-955082612 (deleted)
sh
เห็นไหม สำหรับเชลล์ทุกตัวที่อยู่เหนือเชลล์จะสร้างไฟล์และแม็พกับตัวอธิบายไฟล์ ในzsh, (ba)sh
เชลล์สร้างไฟล์ปกติใน/tmp
ดัมพ์เอาต์พุตแม็พกับ descriptor จากนั้นลบ/tmp
ไฟล์เพื่อให้สำเนาของเคอร์เนล descriptor เป็นสิ่งที่เหลืออยู่ dash
หลีกเลี่ยงเรื่องไร้สาระทั้งหมดและเพียงแค่หยดการประมวลผลของมันลงใน|pipe
ไฟล์ที่ไม่ระบุชื่อมุ่งเป้าไปที่<<
เป้าหมายเปลี่ยนเส้นทาง
สิ่งนี้ทำให้dash
:
cmd <<HEREDOC
$(cmd)
HEREDOC
หน้าที่เทียบเท่ากับbash
:
cmd <(cmd)
ในขณะที่dash
การนำไปใช้งานอย่างน้อยก็พกพาได้ POSIXly
ซึ่งทำให้ไฟล์หลายไฟล์
ดังนั้นในคำตอบด้านล่างเมื่อฉัน:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_fn() { printf '#!' ; command -v zsh ; cat
} <<SCRIPT >$0
[SCRIPT BODY]
SCRIPT
_fn ; exec $0
FILE
สิ่งต่อไปนี้เกิดขึ้น:
ครั้งแรกที่ผมcat
เนื้อหาของสิ่งที่ยื่นเปลือกที่สร้างขึ้นสำหรับFILE
เข้าไป./file
ให้มันปฏิบัติการแล้วรันมัน
เคอร์เนลตีความ#!
และบริการโทร/usr/bin/sh
กับ<read
อธิบายไฟล์./file
มอบหมายให้
sh
แผนที่สตริงในหน่วยความจำประกอบด้วยคำสั่งผสมเริ่มต้นที่และสิ้นสุดที่_fn()
SCRIPT
เมื่อ_fn
ถูกเรียกsh
ต้องตีความแล้ว map เพื่อบ่งไฟล์ที่กำหนดไว้ใน<<SCRIPT...SCRIPT
ก่อนที่จะกล่าวอ้าง_fn
เป็นพิเศษในตัวยูทิลิตี้เพราะSCRIPT
เป็น_fn
's<input.
เอาท์พุทสตริงโดยprintf
และcommand
จะเขียนออกไป_fn
's มาตรฐานออก >&1
- ซึ่งถูกเปลี่ยนเส้นทางไปเปลือกปัจจุบันของARGV0
- $0
หรือ
cat
เชื่อมของ<&0
มาตรฐานการป้อนข้อมูลไฟล์บ่ง - SCRIPT
- เหนือ>
ตัดทอนเปลือกปัจจุบันของการโต้แย้งหรือARGV0
$0
เสร็จสิ้นการอ่านแล้วในปัจจุบันคำสั่งผสม , sh exec
s ปฏิบัติการ - - และเขียนใหม่ที่เพิ่ง$0
โต้แย้ง
จากเวลาที่./file
ถูกเรียกจนกระทั่งคำแนะนำที่มีอยู่ระบุว่ามันควรจะเป็นexec
d อีกครั้งsh
อ่านในคำสั่งผสมเดียวในเวลาที่มันดำเนินการพวกเขาในขณะที่./file
ตัวเองทำอะไรเลยยกเว้นยอมรับเนื้อหาใหม่อย่างมีความสุข ไฟล์ที่ใช้งานจริงคือ/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
ขอบคุณหลังจากทั้งหมด
ดังนั้นเมื่อ @ jw013 ระบุว่า:
ไฟล์ที่รับอินพุตและสร้างเอาต์พุตเป็นเรื่องปกติ ...
... ท่ามกลางการวิพากษ์วิจารณ์ที่ผิดพลาดของคำตอบนี้จริง ๆ แล้วเขาก็ไม่เอาผิดวิธีการเดียวที่ใช้ที่นี่ซึ่งโดยทั่วไปแล้วใช้เพื่อ:
cat <new_file >old_file
ตอบ
คำตอบทั้งหมดที่นี่ดี แต่ไม่มีคำตอบที่ถูกต้องทั้งหมด #!bang
ดูเหมือนว่าทุกคนจะอ้างว่าคุณไม่ได้แบบไดนามิกและอย่างถาวรสามารถเส้นทางของคุณ นี่คือตัวอย่างของการตั้งค่าเส้นทางอิสระ shebang:
การสาธิต
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me ; exec $0
FILE
เอาท์พุท
$0 : ./file
lines : 13
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
12 > SCRIPT
13 > _rewrite_me ; out=$0 _rewrite_me ; exec $0
$0 : /home/mikeserv/file
lines : 8
!bang : #!/usr/bin/zsh
shell : /usr/bin/zsh
1 > #!/usr/bin/zsh
2 > printf "
...
7 > sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
8 > sed -e 'N;s/\n/ >\t/' -e 4a\\...
เห็นไหม เราแค่ทำให้สคริปต์เขียนทับตัวเอง และจะเกิดขึ้นเพียงครั้งเดียวหลังจากการgit
ซิงค์ จากจุดนั้นมันมีเส้นทางที่ถูกต้องใน #! bang line
ตอนนี้เกือบทั้งหมดมีเพียงปุย หากต้องการทำสิ่งนี้อย่างปลอดภัยคุณต้อง:
ฟังก์ชั่นที่กำหนดไว้ที่ด้านบนและเรียกว่าที่ด้านล่างที่จะเขียน วิธีนี้เราเก็บทุกสิ่งที่เราต้องการในหน่วยความจำและให้แน่ใจว่าไฟล์ทั้งหมดถูกอ่านก่อนที่เราจะเริ่มเขียนทับ
วิธีกำหนดเส้นทางที่ควรจะเป็น command -v
ค่อนข้างดีสำหรับสิ่งนั้น
Heredocs ช่วยจริงๆเพราะเป็นไฟล์จริง พวกเขาจะเก็บสคริปต์ของคุณในระหว่างนี้ คุณสามารถใช้สตริงได้เช่นกัน แต่ ...
คุณต้องตรวจสอบให้แน่ใจว่าเชลล์อ่านในคำสั่งที่เขียนทับสคริปต์ของคุณในรายการคำสั่งเดียวกับที่เรียกใช้งาน
ดู:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me
exec $0
FILE
โปรดสังเกตว่าฉันย้ายexec
คำสั่งลงหนึ่งบรรทัดเท่านั้น ขณะนี้:
#OUTPUT
$0 : ./file
lines : 14
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
13 > _rewrite_me ; out=$0 _rewrite_me
14 > exec $0
ฉันไม่ได้รับผลลัพธ์ครึ่งหลังเนื่องจากสคริปต์ไม่สามารถอ่านได้ในคำสั่งถัดไป ยังเพราะคำสั่งเดียวที่ขาดหายไปเป็นครั้งสุดท้าย:
cat ./file
#!/usr/bin/zsh
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
สคริปต์ผ่านมาอย่างที่ควรจะมี - ส่วนใหญ่เป็นเพราะมันอยู่ใน heredoc - แต่ถ้าคุณไม่ได้วางแผนไว้อย่างถูกต้องคุณสามารถตัดทอนภาพยนตร์ของคุณซึ่งเป็นสิ่งที่เกิดขึ้นกับฉันข้างต้น
env
ไม่ว่าทั้ง / bin และ / usr / bin ลองwhich -a env
ยืนยัน