Hãy lấy hai dòng dưới đây cho chúng ta hai kết quả khác nhau.
p=$(cd ~ && pwd) ; echo $p
p=$(cd ~ | pwd) ; echo $p
Làm thế nào để hai khác nhau?
|
chạy trong subshells.
Hãy lấy hai dòng dưới đây cho chúng ta hai kết quả khác nhau.
p=$(cd ~ && pwd) ; echo $p
p=$(cd ~ | pwd) ; echo $p
Làm thế nào để hai khác nhau?
|
chạy trong subshells.
Câu trả lời:
Trong p=$(cd ~ && pwd)
:
Thay thế lệnh $()
, chạy trong một lớp con
cd ~
thay đổi thư mục thành ~
(nhà của bạn), nếu cd
thành công ( &&
) sau đó pwd
in tên thư mục trên STDOUT, do đó chuỗi được lưu trên p
sẽ là thư mục chính của bạn, vd/home/foobar
Trong p=$(cd ~ | pwd)
:
Một lần nữa $()
sinh ra một subshell
Các lệnh trên cả hai mặt của |
chạy trong các khung con tương ứng (và cả hai bắt đầu cùng một lúc)
nên cd ~
được thực hiện trong một subshell, và pwd
trong một riêng biệt subshell
vì vậy bạn sẽ chỉ nhận được STDOUT từ pwd
tức là từ nơi bạn chạy lệnh, đây có thể là bất kỳ thư mục nào bạn có thể tưởng tượng, do đó p
sẽ chứa tên thư mục từ nơi lệnh được gọi, không phải thư mục chính của bạn
cd ~
không tạo ra bất kỳ đầu ra nào và pwd
không đọc bất kỳ đầu vào nào.
(cd ~);p=$(pwd)
nó phải không?
Vấn đề cốt lõi là làm thế nào các toán tử &&
và |
kết nối hai lệnh.
Các &&
kết nối các lệnh thông qua mã thoát. Việc |
kết nối hai lệnh thông qua các mô tả tập tin (stdin, stdout).
Hãy đơn giản hóa trước. Chúng ta có thể loại bỏ bài tập và viết:
echo $(cd ~ && pwd)
echo $(cd ~ | pwd)
Chúng ta thậm chí có thể loại bỏ lớp vỏ thực thi lệnh để phân tích điều này:
$ cd ~ && pwd
$ cd ~ | pwd
Nếu chúng ta thay đổi lời nhắc để hiển thị thư mục nơi các lệnh được thực thi, đại loại như thế PS1='\w\$ '
, chúng ta sẽ thấy điều này:
/tmp/user$ cd ~ && pwd
/home/user
~$
cd ~
đã thay đổi "thư mục hiện tại" thành nhà của người dùng thực tế đang thực thi lệnh ( /home/user
).pwd
thành ~
như được hiển thị bởi dấu nhắc của ~$
.Nếu thay đổi thư mục không thành công (mã thoát không 0) vì một số lý do (thư mục không tồn tại, khối quyền đọc thư mục), lệnh tiếp theo sẽ không được thực thi.
Thí dụ:
/tmp/user$ false && pwd
/tmp/user$ _
Mã thoát của 1 từ false
ngăn chặn việc thực hiện lệnh tiếp theo.
Do đó, mã thoát của "lệnh 1" là những gì ảnh hưởng đến "lệnh 2".
Bây giờ, các hiệu ứng của toàn bộ lệnh:
/tmp/user$ echo $(cd ~ && pwd)
/home/user
/tmp/user$ _
Thư mục đã được thay đổi, nhưng bên trong một vỏ phụ $(…)
, thư mục đã thay đổi được in /home/user
, nhưng ngay lập tức bị loại bỏ khi vỏ phụ đóng lại. Pwd trở lại là thư mục ban đầu ( /tmp/user
).
Đây là những gì sẽ xảy ra:
/tmp/user$ cd ~ | pwd
/tmp/user
/tmp/user$ _
Ký tự meta |
(không phải là toán tử thực) báo hiệu cho shell để tạo ra cái được gọi là "ống", (trong bash) mỗi lệnh ở mỗi bên của ống ( |
) được đặt bên trong mỗi vỏ phụ, đầu tiên là bên phải lệnh, sau đó, một bên trái. Bộ mô tả tệp đầu vào ( /dev/stdin
) của lệnh bên phải được kết nối với bộ mô tả đầu ra ( /dev/stdout
) và sau đó cả hai lệnh được bắt đầu và bên trái để tương tác. Lệnh bên trái ( cd -
) không có đầu ra, và, cũng vậy, lệnh bên phải ( pwd
) không chấp nhận đầu vào. Vì vậy, mỗi cái chạy độc lập bên trong mỗi vỏ con riêng.
cd ~
thay đổi pwd của một vỏ.pwd
bản in pwd (hoàn toàn độc lập) của vỏ phụ khác.Các thay đổi trên mỗi vỏ được loại bỏ khi kết thúc đường ống, vỏ phụ bên ngoài không thay đổi pwd.
Đó là lý do tại sao hai lệnh chỉ được kết nối bằng "mô tả tệp".
Trong trường hợp này, không có gì được gửi, và không có gì được đọc.
Toàn bộ lệnh:
$ echo "$(cd ~ | pwd)"
Sẽ chỉ in thư mục nơi lệnh được thực thi.
Tôi không chắc nếu bạn có nghĩa là '|' hoặc '||' trong trường hợp thứ hai của bạn.
'|' trong shell shell, đầu ra của một lệnh tới đầu vào của một lệnh khác - trường hợp sử dụng phổ biến là một cái gì đó như:
curl http://abcd.com/efgh | grep ijkl
tức là chạy một lệnh và sử dụng một lệnh khác để xử lý đầu ra của lệnh.
Trong ví dụ bạn đưa ra, nó khá phi lý, vì 'cd' thường không tạo ra bất kỳ đầu ra nào và 'pwd' không mong đợi bất kỳ đầu vào nào.
'&&' và '||' là các lệnh đối tác mặc dù. Chúng được thiết kế để được sử dụng giống như các toán tử "và" và "hoặc" logic trong hầu hết các ngôn ngữ. Tuy nhiên, việc tối ưu hóa được thực hiện mang lại cho họ một hành vi cụ thể là mô hình lập trình shell.
Để xác định kết quả của phép toán "và" logic, bạn chỉ cần đánh giá điều kiện thứ hai nếu điều kiện thứ nhất thành công - nếu điều kiện thứ nhất thất bại, kết quả chung sẽ luôn sai.
Để xác định kết quả của thao tác "hoặc" logic, bạn chỉ cần đánh giá điều kiện thứ hai nếu điều kiện thứ nhất không thành công - nếu điều kiện thứ nhất thành công, kết quả chung sẽ luôn đúng.
Vì vậy, trong shell, nếu bạn command1 && command2
command2
sẽ chỉ được thực thi khi command1
đã hoàn thành và trả về mã kết quả thành công. Nếu bạn có command1 || command2
command2
sẽ được thực thi khi command1
hoàn thành nếu command1
trả về mã lỗi.
Một mô hình phổ biến khác là phải có command1
một lệnh kiểm tra - điều này tạo ra một dòng lệnh if / then - ví dụ:
[ "$VAR" = "" ] && VAR="Value if empty"
Là một cách (dài dòng) để gán một giá trị cho một biến nếu nó hiện đang trống.
Có nhiều ví dụ về việc sử dụng quy trình này ở những nơi khác trên Stack Exchange