Làm thế nào để chuyển hướng tập tin bash thành tiêu chuẩn khác với shell (`sh`) trên Linux?


9

Tôi đã viết một tập lệnh chuyển người dùng trong khi chạy và thực thi nó bằng cách sử dụng chuyển hướng tệp thành tiêu chuẩn. Vậy user-switch.shlà ...

#!/bin/bash

whoami
sudo su -l root
whoami

Và chạy nó với bashcho tôi hành vi tôi mong đợi

$ bash < user-switch.sh
vagrant
root

Tuy nhiên, nếu tôi chạy tập lệnh với sh, tôi nhận được đầu ra khác nhau

$ sh < user-switch.sh 
vagrant
vagrant

Tại sao bash < user-switch.shcho đầu ra khác nhau hơn sh < user-switch.sh?

Ghi chú:

  • xảy ra trên hai hộp khác nhau chạy Debian Jessie

1
Không phải là một câu trả lời, nhưng liên quan đến sudo su: unix.stackexchange.com/questions/218169/ Khăn
Kusalananda

1
là / bin / sh = dash trên Debian? Tôi không thể repro trên RHEL trong đó / bin / sh = bash
Jeff Schaller

1
/bin/shtrên (bản sao của tôi) Debian là Dash, vâng. Tôi thậm chí không biết đó là gì cho đến bây giờ. Tôi đã bị mắc kẹt với bash cho đến bây giờ.
popedotninja

1
Các bash hành vi không được bảo đảm để làm việc bằng bất kỳ tài liệu ngữ nghĩa một trong hai.
Charles Duffy

Một số người đã chỉ ra cho tôi ngày hôm nay rằng kịch bản tôi đang chạy đã vi phạm ngữ nghĩa về cách sử dụng bash. Có một vài câu trả lời tuyệt vời cho câu hỏi này, nhưng đáng để chỉ ra rằng một người có lẽ không nên chuyển đổi tập lệnh giữa của người dùng. Ban đầu tôi đã thử làm user-switch.shmột công việc của Jenkins và kịch bản được thực thi. Chạy cùng một kịch bản bên ngoài Jenkins tạo ra các kết quả khác nhau và tôi đã dùng đến việc chuyển hướng để có được hành vi mà tôi thấy trong Jenkins.
popedotninja

Câu trả lời:


12

Một kịch bản tương tự, không có sudo, nhưng kết quả tương tự:

$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta

Với bash, phần còn lại của tập lệnh là đầu vào sed, với dash, shell diễn giải nó.

Chạy stracetrên những cái đó: dashđọc một khối kịch bản (tám kB ở đây, quá đủ để giữ toàn bộ tập lệnh), và sau đó sinh ra sed:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Điều đó có nghĩa là tập tin nằm ở cuối tập tin và sedsẽ không thấy bất kỳ đầu vào nào. Phần còn lại được đệm trong dash. (Nếu tập lệnh dài hơn kích thước khối 8 kB, phần còn lại sẽ được đọc bởi sed.)

Bash, mặt khác, tìm kiếm trở lại đến cuối của lệnh cuối cùng:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
...
lseek(0, -7, SEEK_CUR)                  = 29
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Nếu đầu vào đến từ một đường ống, như ở đây:

$ cat script.sh | bash

tua lại không thể được thực hiện, vì đường ống và ổ cắm không thể tìm kiếm. Trong trường hợp này, Bash quay lại đọc một ký tự đầu vào tại một thời điểm để tránh đọc quá nhiều. ( fd_to_buffered_stream()ininput.c ) Thực hiện một cuộc gọi hệ thống đầy đủ cho mỗi byte về nguyên tắc không hiệu quả lắm. Trong thực tế, tôi không nghĩ rằng các lần đọc sẽ là một chi phí lớn so với thực tế là hầu hết mọi thứ mà trình bao gồm liên quan đến việc sinh ra toàn bộ các quy trình mới.

Một tình huống tương tự là thế này:

echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'

Subshell phải đảm bảo readchỉ đọc lên dòng mới đầu tiên, để headthấy dòng tiếp theo. (Điều này cũng hoạt động với dash.)


Nói cách khác, Bash đi đến các độ dài bổ sung để hỗ trợ đọc cùng một nguồn cho chính tập lệnh và cho các lệnh được thực thi từ nó. dashkhông. Cái zshksh93được đóng gói trong Debian đi cùng với Bash về điều này.


1
Thành thật mà nói, tôi hơi ngạc nhiên khi làm việc.
ilkkachu

cảm ơn vì câu trả lời tuyệt vời Tôi sẽ chạy cả hai lệnh bằng cách sử dụng stracesau và so sánh các đầu ra. Tôi đã không sử dụng cách tiếp cận đó.
popedotninja

2
Yeah, phản ứng của tôi trên đọc câu hỏi là gì ngạc nhiên khi nó không làm việc với bash - tôi đã có nó đệ đi như một cái gì đó chắc chắn sẽ không làm việc. Tôi đoán tôi chỉ đúng một nửa :)
hobbs

12

Shell đang đọc kịch bản từ đầu vào tiêu chuẩn. Trong tập lệnh, bạn chạy một lệnh cũng muốn đọc đầu vào tiêu chuẩn. Đầu vào nào sẽ đi đâu? Bạn không thể nói một cách đáng tin cậy .

Cách shell hoạt động là họ đọc một đoạn mã nguồn, phân tích cú pháp và nếu họ tìm thấy một lệnh hoàn chỉnh, hãy chạy lệnh, sau đó tiến hành phần còn lại của đoạn mã và phần còn lại của tệp. Nếu đoạn mã không chứa lệnh hoàn chỉnh (có ký tự kết thúc ở cuối - tôi nghĩ tất cả các vỏ được đọc đến cuối dòng), vỏ sẽ đọc một đoạn khác, v.v.

Nếu một lệnh trong tập lệnh cố gắng đọc từ cùng một bộ mô tả tệp mà trình bao đang đọc tập lệnh từ đó, thì lệnh sẽ tìm thấy bất cứ điều gì đến sau đoạn cuối mà nó đọc. Vị trí này không thể đoán trước: nó phụ thuộc vào kích thước khối được chọn, và điều đó có thể không chỉ phụ thuộc vào vỏ và phiên bản của nó mà còn vào cấu hình máy, bộ nhớ khả dụng, v.v.

Bash tìm đến cuối mã nguồn của lệnh trong tập lệnh trước khi thực thi lệnh. Đây không phải là thứ mà bạn có thể tin cậy, không chỉ bởi vì các shell khác không làm điều đó, mà còn bởi vì điều này chỉ hoạt động nếu shell đang đọc từ một tệp thông thường. Nếu shell đang đọc từ một đường ống (ví dụ ssh remote-host.example.com <local-script-file.sh), dữ liệu được đọc sẽ được đọc và không thể đọc được.

Nếu bạn muốn chuyển đầu vào cho một lệnh trong tập lệnh, bạn cần thực hiện một cách rõ ràng, điển hình là với một tài liệu ở đây . . nếu bạn mong đợi rằng cái thứ hai whoamisẽ được chuyển làm đầu vào sudo …, hãy nghĩ lại, hãy nhớ rằng hầu hết thời gian tập lệnh không được chuyển đến đầu vào tiêu chuẩn của shell.

#!/bin/bash
whoami
sudo su -l root <<'EOF'
whoami
EOF

Lưu ý rằng thập kỷ này, bạn có thể sử dụng sudo -i root. Chạy sudo sulà một hack từ quá khứ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.