Sự khác biệt giữa && và | trong bash script?


12

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?


@heemayl Cảm ơn. Bạn có thể trích xuất sự khác biệt về trường hợp của tôi? Cảm ơn.
Nam G VU

1
Gợi ý: Các lệnh ở hai bên |chạy trong subshells.
heemayl

Câu trả lời:


21

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 cdthành công ( &&) sau đó pwdin tên thư mục trên STDOUT, do đó chuỗi được lưu trên psẽ 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à pwdtrong một riêng biệt subshell

  • vì vậy bạn sẽ chỉ nhận được STDOUT từ pwdtứ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 đó psẽ 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


Mục đích của đường ống giữa các lệnh là gì? cd ~không tạo ra bất kỳ đầu ra nào và pwdkhông đọc bất kỳ đầu vào nào.
Barmar

Lệnh thứ hai về cơ bản là tương đương với (cd ~);p=$(pwd)nó phải không?
Barmar

@Barmar Vâng, đó là những gì OP đã sử dụng và tôi chỉ giải thích cho anh ấy.
heemayl

6

Vấn đề cốt lõi là làm thế nào các toán tử &&|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
~$ 
  • Lệnh 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).
  • Do kết quả của lệnh đã thành công (mã thoát 0), lệnh tiếp theo sau khi && được thực thi
  • Và "thư mục làm việc hiện tại" được in.
  • Shell đang chạy đã thay đổi pwdthà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ừ falsengă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.

  • Các cd ~thay đổi pwd của một vỏ.
  • Các pwdbả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.


5

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 command2sẽ 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 command2sẽ được thực thi khi command1hoàn thành nếu command1trả về mã lỗi.

Một mô hình phổ biến khác là phải có command1mộ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

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.