LƯU Ý: @ jw013 đưa ra phản đối không được hỗ trợ sau trong các nhận xét bên dưới:
Downvote là bởi vì mã tự sửa đổi thường được coi là thực hành xấu. Quay lại thời kỳ cũ của các chương trình lắp ráp nhỏ, đó là một cách thông minh để giảm các nhánh có điều kiện và cải thiện hiệu suất, nhưng ngày nay các rủi ro bảo mật vượt xa các lợi thế. Cách tiếp cận của bạn sẽ không hoạt động nếu người dùng chạy tập lệnh không có đặc quyền ghi trên tập lệnh.
Tôi đã trả lời sự phản đối an ninh của mình bằng cách chỉ ra rằng bất kỳ điều khoản đặc biệt được chỉ cần một lần mỗi cài đặt / cập nhật hành động để cài đặt / cập nhật các tự cài đặt kịch bản - mà tôi sẽ đích thân gọi khá an toàn. Tôi cũng chỉ cho anh ta một man sh
tài liệu tham khảo để đạt được các mục tiêu tương tự bằng các phương tiện tương tự. Vào thời điểm đó, tôi đã không bận tâm chỉ ra rằng bất kỳ lỗi bảo mật nào hoặc nói chung là các thực tiễn không được nêu rõ trong câu trả lời của tôi, chúng có nhiều khả năng bắt nguồn từ chính câu hỏi hơn là trong câu trả lời của tôi:
Làm cách nào tôi có thể thiết lập shebang để chạy tập lệnh dưới dạng /path/to/script.sh luôn sử dụng Zsh có sẵn trong PATH?
Không hài lòng, @ jw013 tiếp tục phản đối bằng cách tiếp tục cuộc tranh luận chưa được hỗ trợ của mình với ít nhất một vài tuyên bố sai lầm:
Bạn sử dụng một tệp duy nhất, không phải hai tệp. Gói [được man sh
tham chiếu]
có một tệp sửa đổi tệp khác. Bạn có một tập tin sửa đổi chính nó. Có một sự khác biệt rõ ràng giữa hai trường hợp này. Một tập tin nhận đầu vào và sản xuất đầu ra là tốt. Một tập tin thực thi tự thay đổi khi nó chạy thường là một ý tưởng tồi. Ví dụ bạn đã chỉ ra không làm điều đó.
Ở nơi đầu tiên:
CÁC CHỈ thực thi MÃ BÊN TRONG BẤT CỨ thực thi SHELL SCRIPT IS #!
chính nó
(mặc dù chính thức#!
là không xác định )
{ 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
Một kịch bản shell chỉ là một tập tin văn bản - để cho nó có bất kỳ tác dụng ở tất cả nó phải được đọc bởi một tập tin thực thi, hướng dẫn của nó sau đó giải thích bởi rằng tập tin thực thi khác, trước khi cuối cùng là file thực thi khác sau đó thực hiện việc giải thích của nó trong những kịch bản shell. Không thể thực thi tệp shell shell liên quan đến ít hơn hai tệp. Có một ngoại lệ có thể có trong zsh
trình biên dịch riêng của nó, nhưng với điều này tôi có ít kinh nghiệm và nó không có cách nào được trình bày ở đây.
Hashbang của shell script phải trỏ đến trình thông dịch dự định của nó hoặc bị loại bỏ là không liên quan.
Shell có hai chế độ phân tích cơ bản và diễn giải đầu vào của nó: hoặc đầu vào hiện tại của nó đang xác định một <<here_document
hoặc nó đang xác định một { ( command |&&|| list ) ; } &
- nói cách khác, shell sẽ diễn giải mã thông báo như một dấu phân cách cho một lệnh mà nó sẽ thực thi khi nó đã đọc nó trong hoặc như hướng dẫn để tạo một tệp và ánh xạ nó tới một bộ mô tả tệp cho một lệnh khác. Đó là nó.
Khi diễn giải các lệnh để thực thi shell phân định mã thông báo trên một tập hợp các từ dành riêng. Khi vỏ gặp một mở thẻ nó phải tiếp tục đọc trong một danh sách lệnh cho đến khi danh sách được một trong hai giới hạn bởi đường bế thẻ như một dòng mới - khi áp dụng - hoặc đóng cửa mã thông báo như })
cho ({
trước khi thực hiện.
Shell phân biệt giữa một lệnh đơn giản và một lệnh ghép. Lệnh ghép là tập hợp các lệnh phải được đọc trước khi thực thi, nhưng shell không thực hiện $expansion
trên bất kỳ lệnh đơn giản cấu thành nào của nó cho đến khi nó thực thi từng lệnh một.
Vì vậy, trong ví dụ sau, các ;semicolon
từ dành riêng phân định các lệnh đơn giản riêng lẻ trong khi \newline
ký tự không thoát được phân định giữa hai lệnh ghép:
{ 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
Đó là một sự đơn giản hóa của các hướng dẫn. Nó trở nên phức tạp hơn nhiều khi bạn xem xét các shell-shell, subshells, môi trường hiện tại và vv, nhưng, với mục đích của tôi ở đây, nó là đủ.
Và nói về các danh sách dựng sẵn và danh sách lệnh, a function() { declaration ; }
chỉ là một phương tiện để gán một lệnh ghép cho một lệnh đơn giản. Shell không được thực hiện bất kỳ $expansions
trên chính câu lệnh khai báo - bao gồm <<redirections>
- nhưng thay vào đó phải lưu trữ định nghĩa dưới dạng một chuỗi ký tự đơn và thực hiện nó như một trình bao đặc biệt được tích hợp khi được gọi.
Vì vậy, một hàm shell được khai báo trong tập lệnh shell thực thi được lưu trữ trong bộ nhớ của trình dịch phiên dịch ở dạng chuỗi ký tự của nó - chưa được mở rộng để bao gồm các tài liệu ở đây như là đầu vào - và được thực thi độc lập với tệp nguồn của nó mỗi khi nó được gọi là shell được xây dựng- trong chừng nào môi trường hiện tại của vỏ tồn tại.
Các toán tử chuyển hướng <<
và <<-
cả hai đều cho phép chuyển hướng các dòng có trong tệp đầu vào shell, được gọi là tài liệu ở đây, đến đầu vào của lệnh.
Các đây-tài liệu sẽ được coi là một từ duy nhất mà bắt đầu sau khi tiếp theo \newline
và tiếp tục cho đến khi có một dòng chỉ chứa các dấu phân cách và một \newline
, không có [:blank:]
s ở giữa. Sau đó, tài liệu tiếp theo ở đây bắt đầu, nếu có. Định dạng như sau:
[n]<<word
here-document
delimiter
... Trong đó tùy chọn n
đại diện cho số mô tả tập tin. Nếu số bị bỏ qua, tài liệu ở đây đề cập đến đầu vào tiêu chuẩn (mô tả tệp 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
Bạn thấy sao? Đối với mỗi shell phía trên shell sẽ tạo một tệp và ánh xạ nó tới một mô tả tệp. Trong zsh, (ba)sh
shell tạo một tệp thông thường /tmp
, kết xuất kết xuất, ánh xạ nó tới một bộ mô tả, sau đó xóa /tmp
tệp để bản sao của bộ mô tả là tất cả những gì còn lại. dash
tránh tất cả những điều vô nghĩa đó và chỉ cần bỏ quá trình xử lý đầu ra của nó vào một |pipe
tệp ẩn danh nhằm vào <<
mục tiêu chuyển hướng .
Điều này làm cho dash
:
cmd <<HEREDOC
$(cmd)
HEREDOC
chức năng tương đương với bash
's:
cmd <(cmd)
trong khi dash
thực hiện ít nhất là POSIXly xách tay.
MỌI PHIM TUYỆT VỜI
Vì vậy, trong câu trả lời dưới đây khi tôi làm:
{ 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
Sau đây xảy ra:
Tôi lần đầu tiên cat
nội dung của bất cứ tập tin vỏ tạo ra cho FILE
vào ./file
, làm cho nó thực thi, sau đó thực hiện nó.
Nhân thông dịch #!
và các cuộc gọi /usr/bin/sh
với một bộ <read
mô tả tệp được gán cho ./file
.
sh
ánh xạ một chuỗi vào bộ nhớ bao gồm lệnh ghép bắt đầu từ _fn()
và kết thúc tại SCRIPT
.
Khi _fn
được gọi, sh
trước tiên phải giải thích sau đó bản đồ đến một bộ mô tả tập tin quy định tại <<SCRIPT...SCRIPT
trước gọi _fn
là đặc biệt tích hợp tiện ích vì SCRIPT
là _fn
's<input.
Các chuỗi đầu ra bởi printf
và command
được viết ra để _fn
's chuẩn-out >&1
- được chuyển hướng đến vỏ của hiện tại ARGV0
- hoặc $0
.
cat
nối chuỗi mô tả tệp <&0
đầu vào tiêu chuẩn của nó - SCRIPT
- qua đối số >
của shell hiện tại bị cắt bớt ARGV0
, hoặc $0
.
Hoàn thành lệnh ghép hiện tại đã đọc của nó , sh exec
là đối số thực thi - và mới được viết lại - $0
.
Từ lúc ./file
được gọi cho đến khi các lệnh được chứa của nó xác định rằng nó sẽ được exec
lặp lại, sh
đọc nó trong một lệnh ghép duy nhất tại một thời điểm khi nó thực thi chúng, trong khi ./file
bản thân nó không làm gì cả ngoại trừ vui vẻ chấp nhận nội dung mới của nó. Các tập tin thực sự đang làm việc là/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
CẢM ƠN, SAU TẤT CẢ
Vì vậy, khi @ jw013 chỉ định rằng:
Một tập tin nhận đầu vào và tạo đầu ra là tốt ...
... giữa những lời chỉ trích sai lầm của anh ấy về câu trả lời này, anh ấy thực sự đang vô tình bỏ qua phương pháp duy nhất được sử dụng ở đây, về cơ bản chỉ là:
cat <new_file >old_file
CÂU TRẢ LỜI
Tất cả các câu trả lời ở đây là tốt, nhưng không có câu trả lời nào là hoàn toàn chính xác. Mọi người dường như tuyên bố bạn không thể linh hoạt và vĩnh viễn con đường của bạn #!bang
. Đây là một minh chứng về việc thiết lập một shebang độc lập đường dẫn:
BẢN GIỚI THIỆU
{ 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
ĐẦU RA
$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\\...
Bạn thấy sao? Chúng tôi chỉ làm cho kịch bản ghi đè lên chính nó. Và nó chỉ xảy ra một lần sau khi git
đồng bộ hóa. Từ thời điểm đó, nó đã đi đúng đường trong dòng #! Bang.
Bây giờ hầu như tất cả những thứ đó chỉ là lông tơ. Để làm điều này một cách an toàn, bạn cần:
Một chức năng được xác định ở trên cùng và được gọi ở dưới cùng mà viết. Bằng cách này, chúng tôi lưu trữ mọi thứ chúng tôi cần trong bộ nhớ và đảm bảo toàn bộ tệp được đọc trước khi chúng tôi bắt đầu viết lên nó.
Một số cách xác định những gì các con đường nên được. command -v
là khá tốt cho điều đó.
Heredocs thực sự hữu ích vì chúng là các tệp thực tế. Họ sẽ lưu trữ tập lệnh của bạn trong lúc này. Bạn cũng có thể sử dụng chuỗi nhưng ...
Bạn phải chắc chắn rằng trình bao đọc trong lệnh ghi đè lên tập lệnh của bạn trong cùng danh sách lệnh với lệnh thực thi nó.
Nhìn:
{ 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
Lưu ý rằng tôi chỉ di chuyển exec
lệnh xuống một dòng. Hiện nay:
#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
Tôi không nhận được nửa sau của đầu ra vì tập lệnh không thể đọc được trong lệnh tiếp theo. Tuy nhiên, vì lệnh duy nhất bị thiếu là lệnh cuối cùng:
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\\...
Kịch bản xuất hiện đúng như mong muốn - chủ yếu là vì tất cả đều nằm trong di sản - nhưng nếu bạn không lên kế hoạch đúng, bạn có thể cắt đoạn phim của mình, đó là những gì đã xảy ra với tôi ở trên.
env
không có trong cả / bin và / usr / bin không? Hãy thửwhich -a env
xác nhận.