Gán giá trị cho các biến trong một đường ống. Sử dụng lệnh xargs

Điều gì là cần thiết?

Bạn nên làm quen với dòng lệnh cũng như các khái niệm lập trình cơ bản. Mặc dù đây không phải là một hướng dẫn lập trình nhưng nó giải thích (hoặc ít nhất là cố gắng giải thích) nhiều khái niệm cơ bản.
Sử dụng tài liệu này

Tài liệu này có thể cần thiết trong các trường hợp sau:

Bạn có ý tưởng liên quan đến lập trình và có nhu cầu thực hiện quá trình code một số shell script.

Ý tưởng lập trình của bạn chưa đủ cụ thể và cần có hướng dẫn bổ sung.

Bạn có muốn xem một số tập lệnh shell và nhận xét làm ví dụ cho việc tạo tập lệnh của riêng mình không?

Bạn đang di chuyển từ DOS/Windows (hoặc đã làm như vậy) và muốn tạo tập tin xử lý hàng loạt("lô hàng").

Bạn hoàn toàn là một người mọt sách và đọc mọi cách thực hiện có trong tay.

Các kịch bản đơn giản nhất

HƯỚNG DẪN này cố gắng cung cấp cho bạn một số hướng dẫn về lập trình shell, chỉ dựa trên các ví dụ.

Trong phần này, bạn sẽ tìm thấy những đoạn script nhỏ có thể sẽ hữu ích cho bạn khi thành thạo một số kỹ thuật.

Kịch bản "xin chào thế giới" truyền thống

#!/bin/bash echo Xin chào thế giới!

Tập lệnh này chỉ chứa hai dòng. Đầu tiên cho hệ thống biết chương trình nào đang được sử dụng để chạy tệp.

Dòng thứ hai là hành động duy nhất mà tập lệnh này thực hiện, in "Xin chào thế giới" tới thiết bị đầu cuối.

Nếu bạn nhận được thông báo như ./hello.sh: Không tìm thấy lệnh. , thì có lẽ dòng đầu tiên "#!/bin/bash" sai; chạy bash ở đâu hoặc nhìn vào việc tìm bash để tìm ra dòng này sẽ là gì.

Kịch bản sao lưu đơn giản

#!/bin/bash
tar -cZf /var/my-backup.tgz /home/me/

Trong tập lệnh này, thay vì in thông báo trên thiết bị đầu cuối, chúng tôi tạo một kho lưu trữ tar của thư mục chính của người dùng. Kịch bản KHÔNG nhằm mục đích sử dụng thực tế. Tiếp theo trong tài liệu này một kịch bản hiệu quả hơn sẽ được trình bày Dự trữ bản sao.

Tất cả về lý thuyết chuyển hướng và xem nhanh

Có 3 bộ mô tả tệp: stdin - đầu vào tiêu chuẩn, stdout - đầu ra tiêu chuẩn và stderr - lỗi tiêu chuẩn.

Các tính năng chính của bạn:
chuyển hướng thiết bị xuất chuẩn sang tập tin
chuyển hướng stderr sang tập tin
chuyển hướng stdout sang stderr
chuyển hướng stderr sang stdout
chuyển hướng stderr và stdout sang tập tin
chuyển hướng stderr và stdout sang stdout
chuyển hướng stderr và stdout sang stderr

1 có nghĩa là thiết bị xuất chuẩn và 2 có nghĩa là thiết bị xuất chuẩn. Một lưu ý nhanh để hiểu rõ hơn: với lệnh less, bạn có thể xem cả thiết bị xuất chuẩn, vẫn còn trong bộ đệm và thiết bị xuất chuẩn, được in ra màn hình. Tuy nhiên, nó sẽ bị xóa khi bạn cố gắng "duyệt" bộ đệm.

Ví dụ: thiết bị xuất chuẩn cho tập tin

Hành động này ghi đầu ra tiêu chuẩn của chương trình vào một tệp.

ls -l > ls-l.txt

Điều này tạo ra một tệp có tên "ls-l.txt". Nó sẽ chứa mọi thứ mà bạn sẽ thấy nếu bạn chỉ chạy lệnh "ls -l". 3.3 Ví dụ: stderr vào tập tin
Hành động này ghi luồng lỗi tiêu chuẩn của chương trình vào một tệp.
grep da * 2> grep-errors.txt

Điều này tạo ra một tệp có tên "grep-errors.txt". Nó sẽ chứa phần lỗi tiêu chuẩn của đầu ra lệnh "grep;da;*". 3.4 Ví dụ: stdout tới stderr

Hành động này ghi đầu ra tiêu chuẩn của chương trình vào cùng một tệp với luồng lỗi tiêu chuẩn.
grep da * 1>&2

Ở đây đầu ra tiêu chuẩn của lệnh được gửi tới lỗi tiêu chuẩn. Bạn có thể nhìn thấy nó những cách khác. 3.5 Mẫu: stderr 2 stdout

Hành động này ghi luồng lỗi tiêu chuẩn của chương trình vào cùng vị trí với đầu ra tiêu chuẩn.
grep * 2>&1
Ở đây, luồng lỗi tiêu chuẩn của lệnh được gửi đến đầu ra tiêu chuẩn; Nếu bạn đặt kết quả (|) thành ít hơn, bạn sẽ thấy các dòng thường bị mất (do được ghi vào lỗi tiêu chuẩn) sẽ được lưu trong trường hợp này (vì chúng ở trên đầu ra tiêu chuẩn). 3.6 Ví dụ: stderr và stdout vào file

Hành động này đặt tất cả đầu ra của chương trình vào một tệp. Đây là một lựa chọn tốt cho các công việc định kỳ: nếu bạn muốn lệnh chạy hoàn toàn im lặng.
rm -f $(find / -name core) &> /dev/null

Điều này (giả sử cho cron) sẽ xóa bất kỳ tệp nào được gọi là "lõi" trong bất kỳ thư mục nào. Hãy nhớ rằng bạn phải hoàn toàn chắc chắn về chức năng của lệnh nếu bạn muốn ghi đè kết quả đầu ra của nó. 4. Băng tải

Phần này giải thích một cách khá đơn giản và thực tế về cách sử dụng băng tải và lý do bạn có thể cần chúng.
Nó là gì và tại sao bạn nên sử dụng nó?

Đường ống cung cấp cho bạn khả năng sử dụng (tác giả tin rằng điều này khá đơn giản) đầu ra của một chương trình làm đầu vào của chương trình khác.

Ví dụ: một đường ống đơn giản với sed

Đây là một cách rất đơn giản để sử dụng băng tải.

ls -l | sed -e "s//u/g"

Điều xảy ra ở đây là lệnh ls;-l ban đầu được thực thi và đầu ra của nó, thay vì hiển thị trên màn hình, được gửi đến chương trình sed, chương trình này sẽ in những gì cần có trên màn hình. 4.3 Ví dụ: thay thế cho ls;-l;*.txt

Có lẽ nó còn hơn thế nữa con đường gian nan, hơn ls;-l;*.txt, nhưng nó được cung cấp ở đây chỉ để minh họa công việc với các đường dẫn chứ không phải để quyết định lựa chọn giữa hai phương pháp liệt kê này.

ls -l | grep "\.txt$"

Ở đây, đầu ra của ls -l được gửi tới grep, từ đó grep sẽ in các dòng khớp với biểu thức chính quy "\.txt$". 5. Biến

Bạn có thể sử dụng các biến theo cách tương tự như trong bất kỳ ngôn ngữ lập trình nào. Không có kiểu dữ liệu. Biến trong bash có thể là số, ký tự hoặc chuỗi ký tự.

Bạn không nên khai báo một biến. Trong thực tế, việc gán một giá trị cho con trỏ của nó đã tạo ra nó rồi.

Ví dụ: "Xin chào thế giới!" bằng cách sử dụng các biến

#!/bin/bash
STR="Xin chào thế giới!"
tiếng vang $STR

Dòng thứ hai tạo một biến có tên STR và gán cho nó giá trị chuỗi "Xin chào thế giới!". Sau đó, VALUE của biến này được trích xuất bằng cách thêm dấu "$" ở đầu. Hãy nhớ (cố gắng lên) rằng nếu bạn không sử dụng dấu "$", đầu ra của chương trình có thể khác. Có lẽ không phải là thứ bạn cần.
Ví dụ: tập lệnh sao lưu rất đơn giản (hiệu quả hơn)
#!/bin/bash
OF=/var/my-backup-$(date +%Y%m%d).tgz #OF - Tệp đầu ra - tệp đầu ra
tar -cZf $OF /home/me/

Kịch bản này giới thiệu một khái niệm khác. Trước hết, bạn nên giải quyết dòng thứ hai. Hãy chú ý đến biểu thức "$(date +%Y%m%d)". Nếu bạn chạy tập lệnh này, bạn sẽ nhận thấy rằng nó thực thi lệnh bên trong dấu ngoặc đơn, chặn đầu ra của nó.

Lưu ý rằng trong tập lệnh này, tên tệp đầu ra sẽ thay đổi hàng ngày dựa trên định dạng của phím lệnh date;(+%Y%m%d). Bạn có thể thay thế điều này bằng một phép gán định dạng khác.
Những ví dụ khác:
tiếng vang ls
tiếng vang $(ls)

Biến cục bộ

Các biến cục bộ có thể được tạo bằng cách sử dụng từ khóa local.
#!/bin/bash
XIN CHÀO=Xin chào
xin chào(
địa phương HELLO=Thế giới
echo $Xin chào
}
echo $Xin chào
Xin chào
echo $Xin chào
Ví dụ này đủ để chỉ ra cách sử dụng các biến cục bộ.

Câu điều kiện

Câu lệnh có điều kiện cho bạn khả năng quyết định có thực hiện một hành động hay không; quyết định được đưa ra khi tính giá trị của biểu thức.

Chỉ là lý thuyết

tồn tại một số lượng lớn các dạng câu lệnh điều kiện. Dạng cơ bản là một câu lệnh if then, trong đó "câu lệnh" chỉ được thực thi nếu "biểu thức" đánh giá là "true". "2<1" - это выражение, имеющее значение "ложь", в то время как "2>1" - "đúng".

Có các dạng câu lệnh điều kiện khác như: biểu thức if thì câu lệnh 1 câu lệnh khác câu lệnh 2. Ở đây "toán tử1" được thực thi nếu "biểu thức" là đúng; nếu không, "câu lệnh 2" sẽ được thực thi.

Một dạng khác của câu lệnh điều kiện là: nếu biểu thức1 thì toán tử1 khác nếu biểu thức2 thì toán tử2 khác toán tử3. Biểu mẫu này chỉ thêm chuỗi "ELSE IF "biểu thức2" THÌ "câu lệnh2"", khiến "câu lệnh2" được thực thi nếu "biểu thức2" được đánh giá là "đúng". Mọi thứ khác đều tương ứng với ý tưởng của bạn về điều này (xem các biểu mẫu trước đó).

Một vài lời về cú pháp:
Cấu trúc cơ bản của câu lệnh "if" trong bash trông như thế này:
nếu [biểu thức];
sau đó
mã nếu "biểu thức" là đúng.
fi

Ví dụ: ví dụ cơ bản về câu lệnh điều kiện if;..;then
#!/bin/bash
nếu [ "foo" = "foo"]; sau đó

fi

Nếu biểu thức bên trong dấu ngoặc vuông là đúng thì mã được thực thi sẽ nằm sau từ "then" và trước từ "fi", cho biết phần cuối của mã sẽ được thực thi khi đáp ứng điều kiện.
Ví dụ: ví dụ cơ bản về câu lệnh điều kiện if;..;then;...;else
#!/bin/bash
nếu [ "foo" = "foo"]; sau đó
biểu thức echo đánh giá là đúng
khác

fi

Ví dụ: Câu lệnh điều kiện có biến
#!/bin/bash
T1="foo"
T2="thanh"
nếu [ "$T1" = "$T2" ]; sau đó
biểu thức echo đánh giá là đúng
khác
biểu thức echo đánh giá là sai
fi
vòng lặp for, while và cho đến khi
Trong phần này, bạn sẽ làm quen với các vòng lặp for, while và cho đến khi.
Vòng lặp for hơi khác so với vòng lặp trong các ngôn ngữ lập trình khác. Trước hết, nó mang lại cho bạn cơ hội để thực hiện hành động nhất quán phía trên các "từ" trong dòng.
Vòng lặp while thực thi một đoạn mã nếu biểu thức đang được kiểm tra là đúng; và dừng nếu nó sai (hoặc gặp phải ngắt vòng lặp được chỉ định rõ ràng trong mã thực thi).
Vòng lặp cho đến gần giống với vòng lặp while. Sự khác biệt duy nhất là mã được thực thi nếu biểu thức đang được kiểm tra là sai.
Nếu bạn cho rằng while và cho đến khi rất giống nhau thì bạn đã đúng.

Ví dụ cho vòng lặp

#!/bin/bash
cho tôi ở $(ls); LÀM
mục tiếng vang: $i
xong

Trong dòng thứ hai, chúng ta biểu thị i dưới dạng một biến nhận các giá trị khác nhau có trong $(;ls;).

Nếu cần, dòng thứ ba có thể dài hơn; hoặc có thể có vài dòng trước khi hoàn thành (dòng thứ 4).

"done" (dòng thứ 4) cho biết mã sử dụng $i sắp kết thúc và $i được cấp một giá trị mới.

Kịch bản này không nhằm mục đích có tầm quan trọng lớn. Cách sử dụng vòng lặp for hữu ích hơn là sử dụng nó để chỉ chọn một số tệp nhất định trong ví dụ trước.
C-như cho

fiesh đề xuất thêm dạng vòng lặp này. Cái này vòng lặp for, giống nhất với for trong các ngôn ngữ C, Perl, v.v.

#!/bin/bash
for i in `seq 1 10`;
LÀM
tiếng vang $i
xong
Ví dụ vòng lặp while:
#!/bin/bash
BỘ ĐẾM=0
while [ $COUNTER -lt 10 ]; LÀM
echo Bộ đếm là $COUNTER
hãy để COUNTER=COUNTER+1
xong

Tập lệnh này “mô phỏng” cấu trúc “for” nổi tiếng (trong C, Pascal, Perl, v.v.).

Ví dụ cho đến vòng lặp:

#!/bin/bash
BỘ ĐẾM=20
cho đến [$COUNTER -lt 10]; LÀM
echo ĐẾM $COUNTER
hãy để ĐẾM-=1
xong

Chức năng

Cũng giống như bất kỳ ngôn ngữ lập trình nào khác, bạn có thể sử dụng các hàm để nhóm các đoạn mã lại với nhau theo cách hợp lý hơn và để thực hành nghệ thuật đệ quy kỳ diệu.

Khai báo hàm chỉ là một mục nhập hàm my_func ( my_code ).

Việc gọi một hàm được thực hiện tương tự như cách gọi các chương trình khác. Bạn chỉ cần viết tên cô ấy.

Hàm ví dụ
#!/bin/bash
chức năng thoát (
lối ra
}
xin chào(
echo Xin chào!
}
Xin chào
từ bỏ
echo foo
Dòng 2-4 chứa chức năng "thoát". Dòng 5-7 chứa chức năng "xin chào". Nếu bạn không hiểu quy trình được thực hiện bởi tập lệnh này, hãy thử nó!

Cần lưu ý rằng không cần thiết phải khai báo các hàm theo thứ tự cụ thể nào.

Nếu bạn chạy tập lệnh, hãy lưu ý rằng hàm "xin chào" được gọi trước tiên và sau đó là hàm "thoát". Còn chương trình thì không bao giờ tới dòng thứ 10.

Ví dụ về hàm có tham số
#!/bin/bash
chức năng thoát (
lối ra
}
hàm e (
tiếng vang $1
}
e Xin chào
thế giới mạng
từ bỏ
echo foo
Kịch bản này gần giống với kịch bản trước. Sự khác biệt chính là chức năng "e". Nó in ngay đối số đầu tiên mà nó nhận được. Các đối số trong hàm được xử lý giống như các đối số được truyền vào tập lệnh.

Giao diện người dùng

Sử dụng chọn để tạo thực đơn đơn giản
#!/bin/bash
OPTIONS="Xin chào Thoát"
chọn tham gia $OPTIONS; LÀM
if [ "$opt" = "Thoát" ]; sau đó
echo xong
lối ra
elif [ "$opt" = "Xin chào" ]; sau đó
echo Xin chào thế giới
khác
thông thoáng
tiếng vang tùy chọn xấu
fi
xong
Nếu bạn chạy tập lệnh này, bạn sẽ thấy rằng đó là giấc mơ của các lập trình viên về một menu dựa trên văn bản. Có thể bạn sẽ nhận thấy rằng điều này rất giống với cấu trúc "for", nhưng thay vì lặp qua từng "từ" trong $OPTIONS, chương trình sẽ thăm dò ý kiến ​​người dùng.
Sử dụng dòng lệnh
#!/bin/bash
nếu [ -z "$1" ]; sau đó
sử dụng echo: thư mục $0
lối ra
fi
SRCD=$1 #SRCD - Thư mục SouRCe - thư mục nguồn

tar -cZf $TGTD$OF $SRCD
Bạn nên rõ kịch bản này làm gì. Biểu thức trong câu lệnh điều kiện đầu tiên kiểm tra xem chương trình có nhận được đối số ($1) hay không. Nếu không, nó sẽ dừng chương trình và hiển thị cho người dùng một thông báo lỗi nhỏ. Còn lại trên khoảnh khắc này một phần của kịch bản rõ ràng là có thể hiểu được.

Điều khoản khác

Đọc đầu vào của người dùng với read

Trong một số trường hợp, có thể cần phải yêu cầu người dùng nhập nội dung nào đó. Hiện hữu nhiều cách khác nhauđang làm việc này. Một cách là như sau:

#!/bin/bash
echo Vui lòng nhập tên của bạn
đọc TÊN
echo "Xin chào $NAME!"

Ngoài ra, bạn có thể nhận được nhiều giá trị cùng một lúc bằng cách sử dụng read. Ví dụ sau đây giải thích điều này:
#!/bin/bash
echo "Xin vui lòng nhập họ và tên của bạn"
đọc FN LN #FN - Họ - tên; LN - Họ - họ
echo "Xin chào! $LN, $FN !"

Tính toán số học

Tại dòng lệnh (hoặc shell), hãy thử gõ như sau:
tiếng vang;1;+;1
Nếu bạn mong đợi nhìn thấy "2", bạn sẽ thất vọng. Bạn nên làm gì nếu cần BASH để thực hiện các phép tính trên các con số của mình? Giải pháp là thế này:
echo;$((1+1))
Nhờ đó, kết luận sẽ “hợp lý” hơn. Ký hiệu này được sử dụng để đánh giá các biểu thức số học. Bạn cũng có thể làm điều đó như thế này:
tiếng vang;$
Nếu bạn cần sử dụng phân số hoặc toán học phức tạp hơn, bạn có thể sử dụng bc để đánh giá các biểu thức số học.
Khi tác giả chạy "echo;$" trong vỏ lệnh, nó trả về giá trị 0. Điều này là do nếu bash phản hồi, nó chỉ sử dụng các giá trị nguyên. Nếu bạn chạy "echo;3/4|bc;-l", shell sẽ trả về giá trị đúng 0,75.

tìm kiếm bash

Từ tin nhắn từ Mike (xem phần "Lời cảm ơn"):

Bạn luôn sử dụng #!/bin/bash .. Bạn có thể cho một ví dụ về cách khám phá vị trí của bash không.
Tốt nhất nên sử dụng "locate bash", nhưng định vị không có sẵn trên tất cả các máy.
"tìm ./ -name bash" từ thư mục gốc thường hoạt động.
Bạn có thể kiểm tra các vị trí sau:
ls -l /bin/bash
ls -l /sbin/bash
ls -l /usr/local/bin/bash
ls -l /usr/bin/bash
ls -l /usr/sbin/bash
ls -l /usr/local/sbin/bash
(tác giả không thể nghĩ ngay đến bất kỳ thư mục nào khác... Anh ấy đã tìm thấy bash ở hầu hết những nơi này trên nhiều hệ thống khác nhau).

Bạn cũng có thể thử "bash nào".

Lấy giá trị trả về của chương trình

Trong bash, giá trị trả về của chương trình được lưu trữ trong một biến đặc biệt gọi là $?.

Ví dụ này minh họa cách chặn giá trị trả về của một chương trình; tác giả cho rằng thư mục dada không tồn tại (điều này cũng được mike đề xuất).
#!/bin/bash
cd /dada &> /dev/null
echo rv: $?
cd $(pwd) &> /dev/null
echo rv: $?


Chặn đầu ra lệnh

Tập lệnh nhỏ này trình bày tất cả các bảng từ tất cả các cơ sở dữ liệu (giả sử bạn đã cài đặt MySQL). Ngoài ra, bạn nên nghĩ cách chuyển đổi lệnh "mysql" để sử dụng tên người dùng và mật khẩu phù hợp.
#!/bin/bash
DBS=`mysql -uroot -e"hiển thị cơ sở dữ liệu"`
cho b trong $DBS ;
LÀM
mysql -uroot -e"hiển thị bảng từ $b"
xong

Nhiều tập tin nguồn

Bạn có thể chạy nhiều tệp bằng lệnh nguồn.

LÀM__
11. Bàn
11.1 Toán tử so sánh chuỗi
(1) s1 = s2
(2) s1 != s2
(3) s1< s2
(4) s1 > s2
(5) -n s1
(6) -z s1
(1) s1 trùng với s2
(2) s1 không trùng với s2
(3) s1 đứng trước s2 theo thứ tự bảng chữ cái (theo ngôn ngữ hiện tại)
(4) s1 theo thứ tự bảng chữ cái sau s2 (theo ngôn ngữ hiện tại)
(5) s1 không có giá trị rỗng(chứa một ký tự trở lên)
(6) s1 có giá trị bằng 0
11.2 Ví dụ về so sánh chuỗi

So sánh hai chuỗi.
#!/bin/bash
S1="chuỗi"
S2="Chuỗi"
nếu [ $S1=$S2 ];
sau đó
echo "S1("$S1") không bằng S2("$S2")"
fi
nếu [ $S1=$S1 ];
sau đó
echo "S1("$S1") bằng S1("$S1")"
fi
Tại thời điểm này, tác giả cảm thấy cần phải trích dẫn một nhận xét từ email nhận được từ Andreas Beck về việc sử dụng if [ $1 = $2 ].
Đây không phải là một ý tưởng hay, vì nếu $S1 hoặc $S2 dòng trống, Bạn sẽ nhận được lỗi cú pháp. Sẽ dễ chấp nhận hơn nếu sử dụng x$1;=;x$2 hoặc "$1";=;"$2" .
11.3 Toán tử số học
+
-
*
% (phần còn lại)
11.4 Toán tử so sánh số học
-lt (<)
-gt (>)
-le (<=)
-ge (>=)
-eq (==)
-ne (!=)
Lập trình viên C chỉ cần chọn toán tử khớp với toán tử đã chọn trong ngoặc đơn.

Các lệnh hữu ích

Phần này được Kees viết lại (xem phần Lời cảm ơn).

Một số lệnh này thực tế có chứa đầy đủ ngôn ngữ lệnh. Ở đây chỉ giải thích những điều cơ bản về các lệnh như vậy. Để biết thêm thông tin chi tiết Xem xét cẩn thận các trang man cho mỗi lệnh.
sed (trình chỉnh sửa luồng)
Sed là một trình soạn thảo không tương tác. Thay vì thay đổi tệp bằng cách di chuyển con trỏ trên màn hình, bạn nên sử dụng tập lệnh hướng dẫn chỉnh sửa sed cùng với tên tệp bạn đang chỉnh sửa. Bạn cũng có thể coi sed như một bộ lọc. Hãy xem một số ví dụ:
$sed "s/to_be_replaced/replaced/g" /tmp/dummy
Sed thay thế chuỗi "to_be_replaced" bằng chuỗi "được thay thế" bằng cách đọc tệp /tmp/dummy. Kết quả được gửi đến đầu ra tiêu chuẩn (thường là bảng điều khiển), nhưng bạn cũng có thể thêm ">;capture" vào dòng trên để sed gửi đầu ra đến tệp "capture".
$sed 12, 18d /tmp/dummy
Sed hiển thị tất cả các dòng ngoại trừ dòng 12 đến 18. Tập tin gốc không bị thay đổi bởi lệnh này.
awk (thao tác tệp dữ liệu, truy xuất và xử lý văn bản)
Có một số lượng lớn cách triển khai ngôn ngữ lập trình AWK (các trình thông dịch phổ biến nhất là gawk từ dự án GNU và mawk "new awk".) Nguyên tắc khá đơn giản: AWK đang tìm kiếm một mẫu; Đối với mỗi mẫu phù hợp, một số hành động sẽ được thực hiện.
Tác giả đã tạo lại tệp giả chứa các dòng sau:
"kiểm tra123
Bài kiểm tra
tteesstt"

$awk "/test/ (in)" /tmp/dummy
kiểm tra123
Bài kiểm tra
Mẫu AWK tìm kiếm là "kiểm tra" và hành động AWK thực hiện khi gặp một dòng trong /tmp/dummy có chuỗi con "kiểm tra" là "in".
$awk "/test/ (i=i+1) END (print i)" /tmp/dummy
Nếu bạn đang tìm kiếm nhiều mẫu, hãy thay thế văn bản giữa các dấu ngoặc kép bằng "-f;file.awk". Trong trường hợp này, bạn có thể viết tất cả các mẫu và hành động vào tệp "file.awk".
grep (in các dòng khớp với mẫu tìm kiếm)
Chúng ta đã xem xét một số lệnh grep trong các chương trước để hiển thị các dòng khớp với một mẫu. Tuy nhiên, grep có thể làm được nhiều hơn thế.
$grep "tìm cái này" /var/log/messages -c
Chuỗi "tìm cái này" được tìm thấy 12 lần trong /var/log/messages.
wc (đếm dòng, từ và byte)
Trong ví dụ sau, bạn sẽ nhận thấy rằng kết quả đầu ra không như chúng ta mong đợi. Trong trường hợp này, tệp giả chứa văn bản tiếp theo:
" giới thiệu bash
cách kiểm tra tập tin"
$wc --words --lines --bytes /tmp/dummy
2 5 34 /tmp/giả
wc không quan tâm đến thứ tự của các tham số. Nó luôn xuất chúng theo thứ tự tiêu chuẩn:<число;строк><число;слов><число;байтов><имя;файла>.
sắp xếp (sắp xếp các dòng của tệp văn bản)

Trong trường hợp này, tệp giả chứa văn bản sau:
"b
c
Một"
$sort /tmp/giả
Đầu ra trông như thế này:
Một
b
c
Các lệnh không nên đơn giản như vậy :-)
bc (ngôn ngữ lập trình tính toán)
bc thực hiện các phép tính từ dòng lệnh (đầu vào từ một tệp, nhưng không thông qua chuyển hướng hoặc đường dẫn) cũng như từ giao diện người dùng. Ví dụ sau đây cho thấy một số lệnh. Lưu ý rằng tác giả đã sử dụng bc với tùy chọn -q để chặn thông báo nhắc.
$bc -q
1 == 5
0
0.05 == 0.05
1
5 != 5
0
2 ^ 8
256
mét vuông(9)
3
trong khi (i != 9) (
tôi = tôi + 1;
in tôi
}
123456789
từ bỏ
tput (khởi tạo một thiết bị đầu cuối hoặc truy vấn cơ sở dữ liệu terminfo)
Một minh họa nhỏ về khả năng của tput:

$tput cốc 10 4
Dấu nhắc lệnh sẽ xuất hiện ở tọa độ (y10,x4).
đặt lại $tput
Màn hình xóa và lời nhắc xuất hiện tại (y1,x1). Lưu ý rằng (y0,x0) là góc trên cùng bên trái.
$tputcols
80 Hiển thị số lượng ký tự có thể có theo hướng x.
Chúng tôi thực sự khuyên bạn nên làm quen với các chương trình này (tối thiểu). tồn tại số lượng lớn chương trình nhỏ, mang đến cho bạn cơ hội thực hiện một số phép thuật thực sự trên dòng lệnh.
[Một số ví dụ được lấy từ trang hướng dẫn sử dụng hoặc Câu hỏi thường gặp.]

Nhiều tập lệnh hơn

Áp dụng lệnh cho tất cả các file trong một thư mục.

Ví dụ: tập lệnh sao lưu rất đơn giản (hiệu quả hơn)

#!/bin/bash
SRCD="/home/" #SRCD - Thư mục SouRCe - thư mục nguồn
TGTD="/var/backups/" #TGTD - Thư mục TarGeT - thư mục cuối cùng
OF=home-$(date +%Y%m%d).tgz #OF - Tệp đầu ra - tệp đầu ra
tar -cZf $TGTD$OF $SRCD

Chương trình đổi tên tập tin

#!/bin/sh
# renna: đổi tên nhiều tệp bằng các quy tắc đặc biệt
# Tác giả - felix hudson Tháng 1 - 2000

#Trước hết, hãy xem các "chế độ" khác nhau mà chương trình này có.
#Nếu đối số đầu tiên ($1) phù hợp, chúng tôi thực hiện phần này
#chương trình và rời đi.

# Kiểm tra khả năng thêm tiền tố.
nếu [ $1 = p ]; sau đó

#Bây giờ chúng ta chuyển sang biến chế độ ($1) và tiền tố ($2)
tiền tố=$2 ; sự thay đổi ; sự thay đổi

# Phải kiểm tra xem có ít nhất một tệp được chỉ định hay không.
# Ngược lại, tốt hơn là không làm gì hơn là đổi tên không tồn tại
# các tập tin!!

nếu [$1 = ]; sau đó

lối ra 0
fi

# Vòng lặp for này xử lý tất cả các file mà chúng ta đã chỉ định
# chương trình.
# Nó thực hiện một lần đổi tên cho mỗi tệp.
cho tập tin trong $*
LÀM
mv $(file) $prefix$file
xong

#Sau đó chương trình được thoát.
lối ra 0
fi

# Kiểm tra điều kiện thêm hậu tố.
# Mặt khác, phần này gần như giống với phần trước;
# vui lòng xem các bình luận có trong đó.
nếu [ $1 = s ]; sau đó
hậu tố=$2 ; sự thay đổi ; sự thay đổi
nếu [$1 = ]; sau đó
echo "không có tập tin nào được chỉ định"
lối ra 0
fi
cho tập tin trong $*
LÀM
mv $(file) $file$suffix
xong
lối ra 0
fi

# Kiểm tra điều kiện đổi tên bằng thay thế.
nếu [ $1 = r ]; sau đó
sự thay đổi
# Vì lý do bảo mật, tác giả đã đưa vào phần này để không làm hỏng bất kỳ tệp nào nếu người dùng
# không xác định phải làm gì:
nếu [$# -lt 3] ; sau đó
echo "Lỗi; dữ liệu nhập đúng: tệp renna r [biểu thức] [thay thế]..."
lối ra 0
fi

# Cùng xem những thông tin khác
CŨ=$1 ; MỚI=$2 ; sự thay đổi ; sự thay đổi

# Vòng lặp for này tuần tự đi qua tất cả các file mà chúng ta
# được gán cho chương trình.
# Nó thực hiện một lần đổi tên cho mỗi tệp bằng chương trình "sed".
# Cái này chương trình đơn giản từ dòng lệnh, phân tích tiêu chuẩn
# nhập và thay thế biểu hiện thông thườngđến một dòng nhất định.
# Ở đây chúng ta cung cấp cho sed một tên tệp (làm đầu vào tiêu chuẩn) và thay thế
# văn bản bắt buộc.
cho tập tin trong $*
LÀM
new=`echo $(file) | sed s/$(OLD)/$(NEW)/g`
mv $(tập tin) $mới
xong
lối ra 0
fi
# Nếu đến dòng này nghĩa là chương trình đã được cấp
# thông số không hợp lệ. Về vấn đề này, cần giải thích cho người dùng cách
# sử dụng
tiếng vang "sử dụng:"
echo "tập tin renna p [tiền tố].."
echo "tập tin renna s [hậu tố].."
echo "các tập tin renna r [biểu thức] [thay thế].."
lối ra 0
#xong!

Chương trình đổi tên file (đơn giản)
#!/bin/bash
#renames.sh
# chương trình đổi tên đơn giản

tiêu chí=$1
re_match=$2
thay thế=$3

Đối với tôi trong $(ls *$criteria*);
LÀM
src=$i
tgt=$(echo $i | sed -e "s/$re_match/$replace/")
mv $src $tgt
xong

Nếu điều xảy ra khác với điều bạn mong đợi (gỡ lỗi)

Làm cách nào tôi có thể gọi BASH?

Sẽ thật tuyệt nếu thêm vào dòng đầu tiên

#!/bin/bash -x
Điều này sẽ tạo ra một số thông tin đầu ra thú vị.

Về tài liệu

Bạn không nên ngần ngại sửa chữa, bổ sung hoặc bất kỳ điều gì khác mà bạn cảm thấy nên đưa vào tài liệu này. Tác giả sẽ cố gắng cập nhật nó bất cứ khi nào có thể.

Toán tử for-in nhằm mục đích truy cập tuần tự vào các giá trị được liệt kê trong danh sách. Lần lượt mỗi giá trị trong danh sách được gán cho một biến. Cú pháp như sau:

đối với biến trong value_list, thực hiện các lệnh

Hãy xem xét ví dụ nhỏ:

#!/bin/bash cho i trong 0 1 2 3 4 #chúng ta sẽ lần lượt gán các giá trị từ 0 đến 4 cho biến $i làm echo "Số bảng điều khiển là $tôi ">> /dev/pts/$i #Viết dòng "Số bảng điều khiển là $i" vào tệp /dev/pts/$i (tệp thiết bị đầu cuối ảo) done #loop đã hoàn thành thoát 0

Sau khi chạy ví dụ, một dòng có số của nó sẽ xuất hiện trong 5 bảng điều khiển ảo (thiết bị đầu cuối) đầu tiên. Các giá trị từ danh sách được thay thế luân phiên vào biến $i và giá trị của biến này được xử lý theo vòng lặp.

Chu kỳ. trong khi lặp lại

Vòng lặp while phức tạp hơn vòng lặp for-in và được sử dụng để lặp lại các lệnh miễn là một số biểu thức còn đúng (mã trả về = 0). Cú pháp toán tử như sau:

while biểu thức hoặc lệnh trả về mã trả về của lệnh do xong

Hãy xem ví dụ sau về cách hoạt động của vòng lặp:

#!/bin/bash lại =có #gán lại giá trị "có" cho biến trong khi [" $ lần nữa"= "có"] #Chúng ta sẽ lặp lại cho đến khi $again bằng "có" do echo "Xin vui lòng nhập tên:" đọc tên echo "Tên bạn đã nhập là $name" echo "Bạn có muốn tiếp tục không?" đọc lại xong echo "Tạm biệt"

Và bây giờ là kết quả của script:

Ite@ite-desktop:~$ ./bash2_primer1.sh Vui lòng nhập tên: ite Tên bạn đã nhập là it Bạn có muốn tiếp tục không? vâng Vui lòng nhập tên: mihail Tên bạn đã nhập là mihail Bạn có muốn tiếp tục không? không, tạm biệt

Như bạn có thể thấy, vòng lặp chạy cho đến khi chúng ta nhập nội dung nào đó khác ngoài “có”. Giữa do và done, bạn có thể mô tả bất kỳ cấu trúc, toán tử nào, v.v., tất cả chúng sẽ được thực thi trong một vòng lặp. Nhưng bạn nên cẩn thận với vòng lặp này, nếu bạn chạy bất kỳ lệnh nào trong đó mà không thay đổi biến biểu thức, bạn có thể nhận được. bị cuốn vào một vòng lặp vô tận.

Bây giờ về điều kiện sự thật. Sau một thời gian, như trong câu lệnh điều kiện if-then-else, bạn có thể chèn bất kỳ biểu thức hoặc lệnh nào trả về mã trả về và vòng lặp sẽ được thực thi cho đến khi mã trả về = 0! Toán tử "[" tương tự như lệnh kiểm tra, kiểm tra tính đúng đắn của điều kiện được truyền cho nó.

Hãy xem một ví dụ khác, tôi lấy nó từ cuốn sách Advanced Bash scripting. Tôi thực sự thích nó Smile, nhưng tôi đã đơn giản hóa nó một chút. Trong ví dụ này, chúng tôi sẽ giới thiệu một loại vòng lặp UNTIL-DO khác. Đây gần như là một sự tương tự hoàn chỉnh Chu trình WHILE-DO, chỉ được thực thi khi một số biểu thức sai.

Đây là một ví dụ:

#!/bin/bash echo "Nhập tử số:"đọc tiếng vang cổ tức "Nhập mẫu số:"đọc số chia dnd =$dividend #chúng ta sẽ thay đổi các biến số bị chia và số chia, #hãy lưu giữ kiến ​​thức của mình vào các biến khác, bởi vì... Họ cho chúng tôi#sẽ cần dvs =$số chia còn lại =1 cho đến [ " $ còn lại "-eq 0 ] hãy để "số dư = số chia % cổ tức" cổ tức =$ số chia số chia =$ số dư được thực hiện echo "GCD của các số $dnd và $dvs = $dividend "

Kết quả thực thi script:

Ite@ite-desktop:~$ ./bash2_primer3.sh Nhập tử số: 100 Nhập mẫu số: 90 GCD của 100 và 90 = 10

Các hoạt động toán học

hãy ra lệnh.

Lệnh let tạo ra các phép tính toán học qua các con số và các biến số.

Hãy xem một ví dụ nhỏ trong đó chúng tôi thực hiện một số phép tính trên các số đã nhập:

#!/bin/bash echo "Nhập a: " đọc echo "Nhập b: " read b let "c = a + b" #addition echo "a+b= $c"để "c = a / b" #division echo "a/b= $c" hãy để "c<<= 2" #dịch chuyển c sang trái 2 bit tiếng vọng "c sau khi dịch chuyển 2 bit: $c "đặt "c = a % b" # tìm phần dư của a chia cho b tiếng vang " $a / $b . phần còn lại: $c "

Kết quả thực hiện:

Ite@ite-desktop:~$ ./bash2_primer2.sh Nhập a: 123 Nhập b: 12 a+b= 135 a/b= 10 c sau khi dịch 2 chữ số: 40 123 / 12. số dư: 3

Chà, như bạn có thể thấy, không có gì phức tạp cả, danh sách các phép toán là tiêu chuẩn:

Phép cộng
- - phép trừ
* - phép nhân
/ - phân công
** - lũy thừa
% - mô đun (phân chia theo mô đun), phần dư của phép chia

let cho phép bạn sử dụng chữ viết tắt cho các lệnh số học, do đó làm giảm số lượng biến được sử dụng.

Ví dụ: a = a+b tương đương với a +=b, v.v.

Làm việc với các chương trình bên ngoài khi viết tập lệnh shell

Đầu tiên, một số lý thuyết hữu ích.

Chuyển hướng luồng

Bash (giống như nhiều shell khác) có tích hợp sẵn mô tả tập tin: 0 (stdin), 1 (stdout), 2 (stderr).

thiết bị xuất chuẩn - Đầu ra tiêu chuẩn. Mọi thứ mà chương trình xuất ra đều ở đây
stdin - Đầu vào tiêu chuẩn. Đây là tất cả những gì người dùng gõ trong bảng điều khiển
stderr - Đầu ra lỗi tiêu chuẩn.

Đối với các thao tác với các tay cầm này, có các ký tự đặc biệt: > (chuyển hướng đầu ra),< (перенаправление ввода). Оперировать ими не сложно. Например:

mèo/dev/ngẫu nhiên>/dev/null

chuyển hướng đầu ra của cat /dev/random sang /dev/null (một thao tác hoàn toàn vô dụng) hoặc

ls -la > danh sách

ghi nội dung của thư mục hiện tại vào tệp danh sách (hữu ích hơn)

Nếu có nhu cầu nối vào một tập tin (khi sử dụng ">" nó được thay thế), bạn phải sử dụng ">>" thay vì ">"

sudo< my_password

sau khi hỏi sudo mật khẩu, nó sẽ được lấy từ tệp my_password, như thể bạn đã nhập nó từ bàn phím.

Nếu bạn chỉ cần ghi vào tệp các lỗi có thể xảy ra khi chạy chương trình, bạn có thể sử dụng:

./ chương trình_with_error 2 > error_file

số 2 trước ">" có nghĩa là bạn cần chuyển hướng mọi thứ kết thúc bằng bộ mô tả 2 (stderr).

Nếu bạn cần buộc stderr ghi vào thiết bị xuất chuẩn, thì việc này có thể được thực hiện như sau. đường:

./ chương trình_with_error 2 >& 1

ký hiệu "&" có nghĩa là một con trỏ tới bộ mô tả 1 (thiết bị xuất chuẩn)

(Theo mặc định, stderr ghi vào bảng điều khiển nơi người dùng đang làm việc (hay đúng hơn là ghi vào màn hình)).

2. Băng tải

Đường ống là một công cụ rất mạnh để làm việc với bảng điều khiển Bash. Cú pháp rất đơn giản:

đội1 | lệnh 2 - có nghĩa là đầu ra của lệnh 1 sẽ được chuyển làm đầu vào cho lệnh 2

Các đường ống có thể được nhóm thành chuỗi và xuất ra bằng cách sử dụng chuyển hướng đến một tệp, ví dụ:

ls -la | grep "băm" | sắp xếp > sắp xếp_list

Đầu ra của lệnh ls -la được chuyển tới lệnh grep, lệnh này chọn tất cả các dòng có chứa hàm băm từ và chuyển nó tới lệnh sắp xếp, lệnh này ghi kết quả vào tệpsort_list. Mọi thứ khá rõ ràng và đơn giản.

Thông thường, các tập lệnh Bash được sử dụng để tự động hóa một số thao tác thông thường trong bảng điều khiển, do đó đôi khi cần xử lý thiết bị xuất chuẩn của một lệnh và chuyển nó sang stdin sang lệnh khác, trong khi kết quả thực hiện một lệnh phải được xử lý trong một số lệnh. đường. Trong phần này tôi sẽ cố gắng giải thích các nguyên tắc cơ bản khi làm việc với các lệnh bên ngoài bên trong một tập lệnh. Tôi nghĩ rằng tôi đã đưa ra đủ ví dụ và bây giờ tôi chỉ có thể viết những điểm chính.

1. Truyền đầu ra cho một biến

Để ghi đầu ra của một lệnh vào một biến, chẳng hạn, chỉ cần đặt lệnh đó trong dấu ngoặc kép ``

A = ` echo "qwerty" ` echo $a

Kết quả: qwerty

Tuy nhiên, nếu bạn muốn lưu trữ danh sách các thư mục trong một biến, bạn phải xử lý đúng kết quả để đặt dữ liệu vào biến đó. Hãy xem một ví dụ nhỏ:

LIST =` find / svn/ -type d 2>/ dev/ null| awk "(FS="/") (in $4)" | sắp xếp | duy nhất | tr "\n" " " ` cho ONE_OF_LIST trong $LIST thực hiện svnadmin hotcopy / svn/ $ONE_OF_LIST / svn/ temp4backup/ $ONE_OF_LIST xong

Ở đây chúng tôi sử dụng vòng lặp for-do-done để lưu trữ tất cả các thư mục trong thư mục /svn/ bằng lệnh svnadmin hotcopy (trong trường hợp của chúng tôi không quan trọng, chỉ là một ví dụ). Dòng được quan tâm nhất là: LIST=`find /svn/ -type d 2>/dev/null| awk "(FS="/") (in $4)"| sắp xếp|uniq | tr "\n" " "` Trong đó biến LIST được giao nhiệm vụ thực thi lệnh find, được xử lý bởi các lệnh awk,sort, uniq, tr (chúng ta sẽ không xem xét tất cả các lệnh này, vì đây là một bài viết riêng) . Biến LIST sẽ chứa tên của tất cả các thư mục trong thư mục /svn/ được đặt trên một dòng (để đưa nó vào chu trình.

Như bạn có thể thấy, mọi thứ không khó, chỉ cần hiểu nguyên tắc và viết một vài đoạn script của riêng bạn. Để kết thúc bài viết, tôi xin chúc các bạn may mắn khi học BASH và Linux nói chung. Những lời chỉ trích, như thường lệ, được hoan nghênh. Bài viết tiếp theo có thể được dành cho việc sử dụng các chương trình như sed, awk.

Học Linux, 101

Luồng, kênh chương trình và chuyển hướng

Tìm hiểu những điều cơ bản về đường ống Linux

Chuỗi nội dung:

Đánh giá ngắn

Trong bài viết này, bạn sẽ tìm hiểu các kỹ thuật cơ bản để chuyển hướng các luồng I/O tiêu chuẩn trong Linux. Bạn sẽ học:

  • Chuyển hướng các luồng đầu vào/đầu ra tiêu chuẩn: đầu vào tiêu chuẩn, đầu ra tiêu chuẩn và lỗi tiêu chuẩn.
  • Hướng đầu ra của một lệnh tới đầu vào của lệnh khác.
  • Gửi đầu ra tới thiết bị đầu ra tiêu chuẩn (thiết bị xuất chuẩn) và tới một tệp cùng một lúc.
  • Sử dụng đầu ra của lệnh làm đối số cho lệnh khác.

Bài viết này sẽ giúp bạn chuẩn bị tham gia kỳ thi Quản trị viên cấp đầu vào LPI 101 (LPIC-1) và chứa tài liệu từ Mục tiêu 103.4 của Chủ đề 103. Mục tiêu có trọng số là 4.

Về loạt bài này

Chuỗi bài viết này sẽ giúp bạn nắm vững các công việc quản trị hệ điều hành Linux. Bạn cũng có thể sử dụng tài liệu trong các bài viết này để chuẩn bị.

Để xem mô tả các bài viết trong loạt bài này và nhận liên kết đến chúng, vui lòng tham khảo trang web của chúng tôi. Danh sách này được cập nhật liên tục với các bài viết mới khi chúng có sẵn và bao gồm các mục tiêu thi chứng chỉ LPIC-1 mới nhất (tính đến tháng 4 năm 2009). Nếu một bài viết bị thiếu trong danh sách, bạn có thể tìm phiên bản cũ hơn đáp ứng các mục tiêu LPIC-1 trước đó (trước tháng 4 năm 2009) bằng cách tham khảo .

Những điều kiện cần thiết

Để tận dụng tối đa các bài viết của chúng tôi, bạn cần có kiến ​​thức cơ bản về Linux và có một máy tính Linux đang hoạt động có thể chạy tất cả các lệnh bạn gặp phải. Đôi khi các phiên bản khác nhau của chương trình tạo ra kết quả khác nhau, do đó nội dung của danh sách và số liệu có thể khác với những gì bạn thấy trên máy tính.

Chuẩn bị chạy các ví dụ

Làm thế nào để liên hệ với Ian

Ian là một trong những tác giả nổi tiếng và có nhiều tác phẩm nhất của chúng tôi. Kiểm tra (EN) được xuất bản trên devWorks. Bạn có thể tìm thấy thông tin liên hệ tại và kết nối với anh ấy cũng như những cộng tác viên và cộng tác viên khác trên DeveloperWorks của tôi.

Để chạy các ví dụ trong bài viết này, chúng tôi sẽ sử dụng một số tệp được tạo trước đó trong bài viết " ". Nếu bạn chưa đọc bài viết này hoặc chưa lưu những tập tin này, đừng lo lắng! Hãy bắt đầu bằng cách tạo một thư mục mới lpi103-4 và tất cả các tệp cần thiết. Để thực hiện việc này, hãy mở một cửa sổ văn bản và điều hướng đến thư mục chính của bạn. Sao chép nội dung của Liệt kê 1 vào hộp văn bản; do thực thi các lệnh, thư mục con lpi103-4 và tất cả các tệp cần thiết trong đó sẽ được tạo trong thư mục chính của bạn mà chúng tôi sẽ sử dụng trong các ví dụ của mình.

Liệt kê 1. Tạo các tệp cần thiết cho các ví dụ của bài viết này
mkdir -p lpi103-4 && cd lpi103-4 && ( echo -e "1 apple\n2 lê\n3 chuối" > text1 echo -e "9\tplum\n3\tbanana\n10\tapple" > text2 echo "Đây là một câu " !#:* !#:1->text3 chia -l 2 text1 chia -b 17 text2 y; )

Cửa sổ của bạn sẽ trông giống như Liệt kê 2 và thư mục làm việc hiện tại của bạn sẽ là thư mục lpi103-4 mới được tạo.

Liệt kê 2. Kết quả của việc tạo các tệp cần thiết
$ mkdir -p lpi103-4 && cd lpi103-4 && ( > echo -e "1 apple\n2 lê\n3 chuối" > text1 > echo -e "9\tplum\n3\tbanana\n10\tapple"> text2 > echo "Đây là một câu." !#:* !#:1->text3echo "Đây là một câu." "Đây là một câu."">text3 > chia -l 2 text1 > chia -b 17 văn bản2 y ) $

Chuyển hướng đầu vào/đầu ra tiêu chuẩn

Một hệ vỏ Linux, chẳng hạn như Bash, nhận đầu vào và gửi đầu ra dưới dạng các chuỗi hoặc dòng nhân vật. Bất kỳ ký tự nào đều độc lập với các ký tự trước hoặc sau. Các ký hiệu không được tổ chức thành các mục có cấu trúc hoặc các khối có kích thước cố định. Các luồng được truy cập bằng cơ chế I/O bất kể luồng ký tự đến từ đâu hoặc được gửi đến (tệp, bàn phím, cửa sổ, màn hình hoặc thiết bị I/O khác). Linux shell sử dụng ba luồng I/O tiêu chuẩn, mỗi luồng được gán một bộ mô tả tệp cụ thể.

  1. thiết bị xuất chuẩnđầu ra tiêu chuẩn, hiển thị đầu ra lệnh và có số xử lý 1.
  2. lỗi chuẩnluồng lỗi tiêu chuẩn, hiển thị lỗi lệnh và có mô tả 2.
  3. stdinđầu vào tiêu chuẩn, chuyển đầu vào cho các lệnh và có số xử lý là 0.

Luồng đầu vào cung cấp đầu vào (thường là từ bàn phím) cho các lệnh. Luồng đầu ra cung cấp khả năng in các ký tự văn bản, thường là tới thiết bị đầu cuối. Thiết bị đầu cuối ban đầu là thiết bị in ASCII hoặc thiết bị đầu cuối hiển thị, nhưng bây giờ nó thường chỉ là một cửa sổ trên màn hình máy tính.

Nếu bạn đã đọc hướng dẫn "", thì một số tài liệu trong bài viết này sẽ quen thuộc với bạn.

Chuyển hướng đầu ra

Có hai cách để chuyển hướng đầu ra sang một tệp:

N> chuyển hướng đầu ra từ một bộ mô tả tập tin N nộp. Bạn phải có quyền ghi vào tập tin. Nếu tập tin không tồn tại, nó sẽ được tạo. Nếu tệp tồn tại thì tất cả nội dung của nó thường bị hủy mà không có bất kỳ cảnh báo nào. N>> cũng chuyển hướng đầu ra từ bộ mô tả tập tin N nộp. Bạn cũng phải có quyền ghi vào tập tin. Nếu tập tin không tồn tại, nó sẽ được tạo. Nếu tệp tồn tại, đầu ra sẽ được thêm vào nội dung của nó.

Biểu tượng N trong toán tử n> hoặc n>> là mô tả tập tin. Nếu nó không được chỉ định thì thiết bị đầu ra tiêu chuẩn được coi là được sử dụng. Liệt kê 3 thể hiện hoạt động chuyển hướng để phân tách đầu ra tiêu chuẩn và lỗi tiêu chuẩn của lệnh ls, sử dụng các tệp đã được tạo trước đó trong thư mục lpi103-4. Cũng được chứng minh là thêm đầu ra lệnh vào các tệp hiện có.

Liệt kê 3. Chuyển hướng đầu ra
$ ls x* z* ls: không thể truy cập z*: Không có tệp hoặc thư mục như vậy xaa xab $ ls x* z* >stdout.txt 2>stderr.txt $ ls w* y* ls: không thể truy cập w*: Không như vậy tập tin hoặc thư mục yaa yab $ ls w* y* >>stdout.txt 2>>stderr.txt $ cat stdout.txt xaa xab yaa yab $ cat stderr.txt ls: không thể truy cập z*: Không có tập tin hoặc thư mục như vậy ls: không thể truy cập w*: Không có tập tin hoặc thư mục như vậy

Chúng tôi đã nói rằng việc chuyển hướng đầu ra bằng toán tử n> thường dẫn đến việc ghi đè các tệp hiện có. Bạn có thể kiểm soát thuộc tính này bằng cách sử dụng tùy chọn noclobber của lệnh tích hợp sẵn. Nếu tùy chọn này được xác định, bạn có thể ghi đè nó bằng toán tử n>|, như trong Liệt kê 4.

Liệt kê 4. Chuyển hướng đầu ra bằng tùy chọn noclobber
$ set -o noclobber $ ls x* z* >stdout.txt 2>stderr.txt -bash: stdout.txt: không thể ghi đè lên tập tin hiện có $ ls x* z* >|stdout.txt 2>|stderr.txt $ cat stdout.txt xaa xab $ cat stderr.txt ls: không thể truy cập z*: Không có tệp hoặc thư mục như vậy $ set +o noclobber #restore cài đặt noclobber gốc

Đôi khi bạn có thể muốn chuyển hướng cả đầu ra tiêu chuẩn và lỗi tiêu chuẩn sang một tệp. Điều này thường được sử dụng trong các quy trình tự động hoặc các công việc nền để có thể xem xét kết quả công việc sau này. Để chuyển hướng đầu ra tiêu chuẩn và lỗi tiêu chuẩn đến cùng một vị trí, hãy sử dụng toán tử &> hoặc &>>. Tùy chọn thay thế là chuyển hướng bộ mô tả tệp N và sau đó là bộ mô tả tập tin tôiđến cùng một nơi bằng cách sử dụng cấu trúc m>&n hoặc m>>&n. Trong trường hợp này, thứ tự chuyển hướng các luồng là quan trọng. Ví dụ, lệnh
lệnh 2>&1 >output.txt
nó không giống như một mệnh lệnh
lệnh >output.txt 2>&1
Trong trường hợp đầu tiên, luồng lỗi stderr được chuyển hướng đến vị trí hiện tại của luồng stdout và sau đó luồng stdout được chuyển hướng đến tệp đầu ra.txt; tuy nhiên, chuyển hướng thứ hai chỉ ảnh hưởng đến thiết bị xuất chuẩn chứ không ảnh hưởng đến thiết bị xuất chuẩn. Trong trường hợp thứ hai, luồng stderr được chuyển hướng đến vị trí hiện tại của luồng stdout, tức là tới tệp đầu ra.txt. Những chuyển hướng này được minh họa trong Liệt kê 5. Lưu ý trong lệnh cuối cùng rằng đầu ra tiêu chuẩn đã được chuyển hướng sau luồng lỗi tiêu chuẩn và kết quả là luồng lỗi tiếp tục được xuất ra cửa sổ đầu cuối.

Liệt kê 5. Chuyển hướng hai luồng sang một tệp
$ ls x* z* &>output.txt $ cat output.txt ls: không thể truy cập z*: Không có tệp hoặc thư mục như vậy xaa xab $ ls x* z* >output.txt 2>&1 $ cat out.txt ls: không thể truy cập z*: Không có tập tin hoặc thư mục như vậy xaa xab $ ls x* z* 2>&1 >output.txt # stderr không đi đến đầu ra.txt ls: không thể truy cập z*: Không có tập tin hoặc thư mục như vậy $ cat đầu ra. txt xaa xab

Trong các tình huống khác, bạn có thể muốn bỏ qua hoàn toàn đầu ra tiêu chuẩn hoặc lỗi tiêu chuẩn. Để thực hiện việc này, hãy chuyển hướng luồng tương ứng tới tệp trống /dev/null. Liệt kê 6 cho thấy cách bỏ qua luồng lỗi từ lệnh ls và cách sử dụng lệnh cat để xác minh rằng tệp /dev/null trên thực tế là trống.

Liệt kê 6. Bỏ qua lỗi tiêu chuẩn bằng cách sử dụng /dev/null
$ ls x* z* 2>/dev/null xaa xab $ cat /dev/null

Chuyển hướng đầu vào

Giống như chúng ta có thể chuyển hướng stdout và stderr, chúng ta có thể chuyển hướng stdin từ một tệp bằng toán tử<. Если вы прочли руководство " ", то должны помнить, что в разделе была использована команда tr для замены пробелов в файле text1 на символы табуляции. В том примере мы использовали вывод команды cat чтобы создать стандартный поток ввода для команды tr . Теперь для преобразования пробелов в символы табуляции вместо бесполезного вызова команды cat мы можем использовать перенаправление ввода, как показано в листинге 7.

Liệt kê 7. Chuyển hướng đầu vào
$ tr " " "\t"

Trình thông dịch lệnh, bao gồm cả bash, triển khai khái niệm này tài liệu ở đây, đây là một trong những cách để chuyển hướng đầu vào. Nó sử dụng thiết kế<< и какое-либо слово, например END, являющееся маркером, или сигнальной меткой, означающей конец ввода. Эта концепция продемонстрирована в листинге 8.

Liệt kê 8. Chuyển hướng đầu vào bằng cách sử dụng khái niệm here-document
$sắp xếp -k2<1 quả táo > 2 quả lê > 3 quả chuối > KẾT THÚC 1 quả táo 3 quả chuối 2 quả lê

Nhưng tại sao bạn không thể gõ lệnh Sort -k2, nhập dữ liệu và nhấn tổ hợp Ctrl-d, cho biết sự kết thúc của đầu vào? Tất nhiên, bạn có thể chạy lệnh này, nhưng khi đó bạn sẽ không biết về khái niệm tài liệu ở đây, rất phổ biến trong các tập lệnh shell (trong đó không có cách nào khác để chỉ định dòng nào sẽ được chấp nhận làm đầu vào). Vì các tab được sử dụng rộng rãi trong các tập lệnh để căn chỉnh văn bản và làm cho chúng dễ đọc hơn nên có một kỹ thuật khác để sử dụng khái niệm tài liệu ở đây. Khi sử dụng toán tử<<- вместо оператора << начальные символы табуляции удаляются.

Trong Liệt kê 9, chúng tôi đã sử dụng tính năng thay thế lệnh để tạo một ký tự tab và sau đó tạo một tập lệnh shell nhỏ chứa hai lệnh cat, mỗi lệnh đọc dữ liệu từ khối tài liệu ở đây. Lưu ý rằng chúng tôi đã sử dụng từ END để báo hiệu khối tài liệu ở đây mà chúng tôi đang đọc từ thiết bị đầu cuối. Nếu chúng tôi sử dụng cùng từ này trong tập lệnh của mình, dữ liệu nhập của chúng tôi sẽ kết thúc sớm. Vì vậy, thay vì từ END, chúng tôi sử dụng từ EOF trong tập lệnh. Khi tập lệnh của chúng tôi được tạo, chúng tôi sử dụng lệnh. (dấu chấm) để chạy nó trong ngữ cảnh của shell hiện tại.

Liệt kê 9. Chuyển hướng đầu vào bằng cách sử dụng khái niệm here-document
$ ht=$(echo -en "\t") $ cat<ex-here.sh > con mèo<<-EOF >apple > EOF > $(ht)cat<<-EOF >$(ht)pear > $(ht)EOF > END $ cat ex-here.sh cat<<-EOF apple EOF cat <<-EOF pear EOF $ . ex-here.sh apple pear

Trong các bài viết tiếp theo của loạt bài này, bạn sẽ tìm hiểu thêm về thay thế lệnh và viết kịch bản. Liên kết tới tất cả các bài viết trong loạt bài này có thể được tìm thấy trong.

Tạo đường ống

Sử dụng lệnh xargs

Lệnh xargs đọc dữ liệu từ thiết bị đầu vào tiêu chuẩn, sau đó xây dựng và thực thi các lệnh lấy đầu vào nhận được làm tham số. Nếu không có lệnh nào được chỉ định thì lệnh echo sẽ được sử dụng. Liệt kê 12 cho thấy một ví dụ đơn giản về cách sử dụng tệp text1 của chúng tôi, tệp này chứa ba dòng, mỗi dòng có hai từ.

Liệt kê 12. Sử dụng lệnh xargs
$ cat text1 1 quả táo 2 quả lê 3 quả chuối $ xargs

Tại sao đầu ra xargs chỉ chứa một dòng? Theo mặc định, xargs sẽ phân chia đầu vào nếu nó gặp các ký tự phân cách và mỗi đoạn kết quả sẽ trở thành một tham số riêng. Tuy nhiên, khi xargs xây dựng một lệnh, nó sẽ được truyền nhiều tham số nhất có thể cùng một lúc. Hành vi này có thể được thay đổi bằng cách sử dụng tùy chọn –n hoặc --max-args. Liệt kê 13 cho thấy một ví dụ về cả hai tùy chọn; một lệnh gọi rõ ràng đến lệnh echo cũng được thực hiện để sử dụng với xargs.

Liệt kê 13. Sử dụng lệnh xargs và echo
$xargs " args > 1 quả táo 2 quả lê 3 quả chuối $ xargs --max-args 3 " args > 1 quả táo 2 args > quả lê 3 quả chuối $ xargs -n 1 " args > 1 args > apple args > 2 args > lê args > 3 args > chuối

Nếu dữ liệu đầu vào chứa khoảng trắng nhưng chúng được đặt trong một hoặc dấu ngoặc kép(hoặc khoảng trắng được biểu diễn dưới dạng chuỗi thoát bằng dấu gạch chéo ngược), khi đó xargs sẽ không chia chúng thành các phần riêng biệt. Điều này được thể hiện trong Liệt kê 14.

Liệt kê 14. Sử dụng lệnh xargs và dấu ngoặc kép
$ echo ""4 mận"" | cat text1 - 1 quả táo 2 quả lê 3 quả chuối "4 quả mận" $ echo ""4 quả mận"" | văn bản mèo1 - | xargs -n 1 1 quả táo 2 quả lê 3 quả chuối 4 quả mận

Cho đến bây giờ, tất cả các đối số đã được thêm vào cuối lệnh. Nếu bạn cần thêm các đối số tùy chọn khác vào sau chúng, hãy sử dụng tùy chọn -I để chỉ định chuỗi thay thế. Tại thời điểm trong lệnh được gọi thông qua xargs nơi chuỗi thay thế được sử dụng, thay vào đó, một đối số sẽ được thay thế. Với cách tiếp cận này, chỉ có một đối số được truyền cho mỗi lệnh. Tuy nhiên, đối số sẽ được tạo từ toàn bộ chuỗi đầu vào chứ không phải từ một đoạn riêng biệt của chuỗi đó. Bạn cũng có thể sử dụng tùy chọn -L của lệnh xargs, điều này sẽ khiến toàn bộ chuỗi được sử dụng làm đối số, thay vì các phần riêng lẻ được phân tách bằng dấu cách. Việc sử dụng tùy chọn -I ngầm khiến tùy chọn -L 1 được sử dụng. Liệt kê 15 cho thấy các ví dụ về cách sử dụng các tùy chọn -I và –L.

Liệt kê 15. Sử dụng lệnh xargs và các dòng đầu vào
$ xargs -I XYZ echo "BẮT ĐẦU XYZ LẶP LẠI XYZ KẾT THÚC" " <9 plum> <3 banana><3 banana> <10 apple><10 apple>$ văn bản mèo1 văn bản2 | xargs -L2 1 quả táo 2 quả lê 3 quả chuối 9 quả mận 3 quả chuối 10 quả táo

Mặc dù các ví dụ của chúng tôi sử dụng các tệp văn bản đơn giản nhưng bạn sẽ không thường xuyên sử dụng lệnh xargs cho những trường hợp như vậy. Thông thường, bạn sẽ xử lý một danh sách lớn các tệp được tạo ra từ các lệnh như ls , find hoặc grep . Liệt kê 16 cho thấy một cách để chuyển xargs danh sách nội dung của một thư mục tới một lệnh như grep.

Liệt kê 16. Sử dụng lệnh xargs và danh sách tệp
$ ls |xargs grep "1" text1:1 apple text2:10 apple xaa:1 apple yaa:1

Trong ví dụ trước, điều gì xảy ra nếu một hoặc nhiều tên tệp chứa dấu cách? Nếu bạn cố gắng sử dụng lệnh như trong Liệt kê 16, bạn sẽ gặp lỗi. Trong tình huống thực tế, danh sách các tệp có thể không được lấy từ lệnh ls, nhưng, ví dụ, do thực thi tập lệnh hoặc lệnh của người dùng; hoặc bạn có thể muốn xử lý nó ở các giai đoạn khác trong quy trình để lọc bổ sung. Vì vậy, chúng tôi không tính đến thực tế là bạn có thể chỉ cần sử dụng grep "1" * thay vì cấu trúc logic hiện có.

Trong trường hợp lệnh ls, bạn có thể sử dụng tùy chọn --quoting-style để buộc tên tệp chứa khoảng trắng được đặt trong dấu ngoặc đơn (hoặc thoát). Giải pháp tốt hơn (khi có thể) là sử dụng tùy chọn -0 của lệnh xargs, tùy chọn này khiến các ký tự trống (\0) được sử dụng để phân tách các đối số đầu vào. Mặc dù lệnh ls không có tùy chọn sử dụng tên tệp kết thúc bằng null làm đầu ra nhưng nhiều lệnh có thể thực hiện việc này.

Trong Liệt kê 17, trước tiên chúng ta sẽ sao chép tệp text1 sang "text 1" và sau đó đưa ra một số ví dụ về cách sử dụng danh sách tên tệp chứa khoảng trắng bằng lệnh xargs. Những ví dụ này giúp bạn hiểu ý tưởng vì có thể không dễ dàng để hoàn toàn thành thạo cách làm việc với xargs. Đặc biệt, ví dụ cuối cùng về chuyển đổi dòng mới thành ký tự trống sẽ không hoạt động nếu một số tên tệp đã chứa dòng mới. Trong phần tiếp theo của bài viết này, chúng ta sẽ xem xét một giải pháp mạnh mẽ hơn, sử dụng lệnh find để tạo đầu ra phù hợp sử dụng các ký tự null làm dấu phân cách.

Liệt kê 17. Sử dụng lệnh xargs và các tệp chứa khoảng trắng trong tên của chúng
$ cp text1 "text 1" $ ls *1 |xargs grep "1" # error text1:1 apple grep: text: Không có tập tin hoặc thư mục như vậy grep: 1: Không có tập tin hoặc thư mục như vậy $ ls --quoting-style escape * 1 text1 text\ 1 $ ls -- shell kiểu trích dẫn *1 text1 "text 1" $ ls -- shell kiểu trích dẫn *1 |xargs grep "1" text1:1 apple text 1:1 apple $ # Minh họa -0 tùy chọn xargs $ ls *1 | tr "\n" "\0" |xargs -0 grep "1" text1:1 apple text 1:1 apple

Lệnh xargs không thể xây dựng các lệnh dài tùy ý. Do đó, trong Linux, cho đến phiên bản kernel 2.26.3, độ dài lệnh tối đa bị giới hạn. Nếu bạn cố chạy một lệnh như rm somepath/* và thư mục chứa nhiều tệp có tên dài, lệnh đó có thể không thành công với lỗi cho biết danh sách đối số quá dài. Nếu bạn đang chạy các phiên bản Linux hoặc UNIX cũ hơn có thể có những hạn chế này, thì có thể hữu ích nếu bạn biết cách sử dụng xargs theo cách khắc phục chúng.

Bạn có thể sử dụng tùy chọn --show-limits để xem giới hạn mặc định cho lệnh xargs và tùy chọn -s để đặt độ dài tối đa của đầu ra lệnh. Bạn có thể tìm hiểu về các tùy chọn khác từ trang man.

Sử dụng lệnh find với tùy chọn -exec hoặc kết hợp với lệnh xargs

Trong phần hướng dẫn " ", bạn đã học cách sử dụng lệnh find để tìm các tệp dựa trên tên, thời gian sửa đổi, kích thước và các đặc điểm khác của chúng. Thông thường, một số hành động nhất định phải được thực hiện trên các tệp được tìm thấy - xóa, sao chép, đổi tên chúng, v.v. Bây giờ chúng ta sẽ xem xét tùy chọn -exec của lệnh find, hoạt động tương tự như lệnh find và sau đó chuyển đầu ra sang lệnh xargs.

Liệt kê 18. Sử dụng tùy chọn find với -exec
$ tìm văn bản -exec cat text3()\; Đây la một câu. Đây la một câu. Đây la một câu. 1 quả táo 2 quả lê 3 quả chuối Đây là một câu. Đây la một câu. Đây la một câu. 9 quả mận 3 quả chuối 10 quả táo

So sánh kết quả của Liệt kê 18 với những gì bạn đã biết về xargs sẽ thấy một vài điểm khác biệt.

  1. Bạn phải sử dụng ký hiệu () trong lệnh để chỉ ra vị trí thay thế nơi tên tệp sẽ được thay thế. Những ký tự này không được tự động thêm vào cuối lệnh.
  2. Bạn phải kết thúc lệnh bằng dấu chấm phẩy, dấu chấm này phải được thoát (\;, ";" hoặc ";").
  3. Lệnh được thực thi một lần cho mỗi tệp đầu vào.

Hãy thử chạy find text |xargs cat text3 để thấy sự khác biệt.

Bây giờ chúng ta hãy quay lại trường hợp tên tệp chứa khoảng trắng. Trong Liệt kê 19, chúng tôi đã thử sử dụng lệnh find với tùy chọn -exec thay vì lệnh ls và xargs.

Liệt kê 19. Sử dụng lệnh find với tùy chọn -exec và các tệp chứa khoảng trắng trong tên của chúng
$ tìm. -name "*1" -exec grep "1" () \; 1 quả táo 1 quả táo

Càng xa càng tốt. Tuy nhiên, bạn có nghĩ rằng còn thiếu điều gì đó ở đây không? Những tập tin nào chứa các dòng được tìm thấy bởi grep? Điều còn thiếu ở đây là tên tệp vì find gọi grep một lần cho mỗi tệp và grep, là một lệnh thông minh, biết rằng nếu nó chỉ được đặt tên của một tệp thì nó không cần cho bạn biết đó là gì.

Trong tình huống này, chúng tôi có thể sử dụng lệnh xargs, nhưng chúng tôi đã biết về sự cố với các tệp có tên chứa dấu cách. Chúng tôi cũng đã đề cập đến thực tế là lệnh find có thể tạo danh sách tên được phân cách bằng null nhờ tùy chọn -print0. Các phiên bản hiện đại của lệnh find có thể được phân tách không phải bằng dấu chấm phẩy mà bằng dấu +, để có thể chuyển số lượng tên tối đa có thể có trong một lệnh gọi tới lệnh find, giống như khi sử dụng xargs . Không cần phải nói, trong trường hợp này bạn chỉ có thể sử dụng cấu trúc () một lần và đó phải là tham số cuối cùng của lệnh. Liệt kê 20 thể hiện cả hai phương pháp này.

Liệt kê 20. Sử dụng find, xargs và các tệp chứa khoảng trắng trong tên của chúng
$ tìm. -name "*1" -print0 |xargs -0 grep "1" ./text 1:1 apple ./text1:1 apple $ find . -name "*1" -exec grep "1" () + ./text 1:1 apple ./text1:1 apple

Cả hai phương pháp này đều hoạt động và việc lựa chọn một trong số chúng thường chỉ được xác định bởi sở thích cá nhân của người dùng. Xin lưu ý rằng bạn có thể gặp sự cố khi sắp xếp các đối tượng có dấu phân cách thô và khoảng trắng; vì vậy nếu bạn chuyển đầu ra cho xargs , hãy sử dụng tùy chọn -print0 của find , cũng như tùy chọn -0 của xargs , tùy chọn này sẽ yêu cầu bạn sử dụng dấu phân cách null trong đầu vào. Các lệnh khác, bao gồm tar, cũng hỗ trợ tùy chọn -0 và hoạt động với đầu vào được phân cách bằng null, vì vậy bạn phải luôn sử dụng tùy chọn này cho các lệnh hỗ trợ nó, trừ khi bạn chắc chắn 100% rằng danh sách đầu vào sẽ không gây ra vấn đề gì cho bạn.

Nhận xét cuối cùng của chúng tôi liên quan đến việc làm việc với danh sách các tệp. Bạn nên luôn kiểm tra danh sách và lệnh của mình một cách cẩn thận trước khi thực hiện các thao tác hàng loạt (chẳng hạn như xóa hoặc đổi tên nhiều tệp). Có một bản sao lưu cập nhật khi cần thiết cũng có thể là vô giá.

Tách đầu ra

Để kết thúc bài viết này, chúng ta sẽ xem nhanh một lệnh nữa. Đôi khi bạn có thể cần xem kết quả đầu ra trên màn hình và lưu nó vào một tệp cùng một lúc. Đối với điều này bạn chúng ta có thể chuyển hướng đầu ra của lệnh đến một tệp trong một cửa sổ, sau đó sử dụng tail -fn1 để theo dõi đầu ra trong một cửa sổ khác, nhưng cách dễ nhất là sử dụng lệnh tee.

Lệnh tee được sử dụng trong một đường ống và đối số của nó là tên của tệp (hoặc tên của một số tệp) mà đầu ra tiêu chuẩn sẽ được dẫn đến. Tùy chọn -a cho phép bạn không thay thế nội dung cũ của tệp bằng nội dung mới mà thêm dữ liệu vào cuối tệp. Như đã thảo luận khi chúng ta thảo luận về đường ống, nếu bạn muốn lưu trữ cả luồng đầu ra tiêu chuẩn và luồng lỗi, bạn phải chuyển hướng stderr sang stdout trước khi chuyển dữ liệu làm đầu vào cho lệnh tee. Liệt kê 21 cho thấy một ví dụ về cách sử dụng lệnh tee để lưu kết quả đầu ra vào hai tệp, f1 và f2.

Liệt kê 21. Tách thiết bị xuất chuẩn bằng lệnh tee
$ ls text|tee f1 f2 text1 text2 text3 $ cat f1 text1 text2 text3 $ cat f2 text1 text2 text3 Pipe là một kênh một chiều để giao tiếp giữa các quá trình. Thuật ngữ này được Douglas McIlroy đặt ra cho Unix shell và được đặt theo tên của một đường dẫn. Các đường ống thường được sử dụng nhiều nhất trong các tập lệnh shell để liên kết nhiều lệnh bằng cách chuyển hướng đầu ra của một lệnh (stdout) sang đầu vào (stdin) của lệnh tiếp theo, sử dụng ký hiệu ống '|':
cmd1 | cmd2 | .... | cmdN
Ví dụ:
$ grep -i “lỗi” ./log | wc -l 43
grep thực hiện tìm kiếm không phân biệt chữ hoa chữ thường đối với chuỗi “lỗi” trong tệp nhật ký, nhưng kết quả tìm kiếm không được in ra màn hình mà được chuyển hướng đến đầu vào (stdin) của lệnh wc, sau đó thực hiện một số dòng.

Logic

Đường dẫn cung cấp khả năng thực thi lệnh không đồng bộ bằng cách sử dụng bộ đệm I/O. Do đó, tất cả các nhóm trong quy trình đều làm việc song song, mỗi nhóm có quy trình riêng.

Kích thước bộ đệm kể từ phiên bản kernel 2.6.11 là 65536 byte (64Kb) và bằng một trang bộ nhớ trong các kernel cũ hơn. Khi cố gắng đọc từ bộ đệm trống, quá trình đọc sẽ chặn cho đến khi dữ liệu xuất hiện. Tương tự như vậy, nếu bạn cố gắng ghi vào bộ đệm đầy, quá trình ghi sẽ bị chặn cho đến khi dung lượng cần thiết được giải phóng.
Điều quan trọng là mặc dù thực tế là đường dẫn hoạt động trên các bộ mô tả tệp của luồng I/O, nhưng tất cả các thao tác đều được thực hiện trong bộ nhớ mà không tải lên đĩa.
Tất cả thông tin bên dưới là dành cho shell bash-4.2 và kernel 3.10.10.

Gỡ lỗi đơn giản

Tiện ích strace cho phép bạn theo dõi các cuộc gọi hệ thống trong quá trình thực hiện chương trình:
$ strace -f bash -c ‘/bin/echo foo | thanh grep' .... getpid() = 13726<– PID основного процесса... pipe() <– системный вызов для создания конвеера.... clone(....) = 13727 <– подпроцесс для первой команды конвеера (echo) ... execve("/bin/echo", ["/bin/echo", "foo"], ..... clone(....) = 13728 <– подпроцесс для второй команды (grep) создается так же основным процессом... stat("/home/aikikode/bin/grep", ... Видно, что для создания конвеера используется системный вызов pipe(), а также, что оба процесса выполняются параллельно в разных потоках.

Rất nhiều mã nguồn bash và kernel

Mã nguồn, cấp 1, shell

Vì tài liệu tốt nhất là mã nguồn nên hãy chuyển sang nó. Bash sử dụng Yacc để phân tích các lệnh đầu vào và trả về 'command_connect()' khi gặp ký tự '|'.
phân tích cú pháp.y :
Đường dẫn 1242: đường dẫn '|' đường dẫn newline_list 1243 ( $$ = command_connect ($1, $4, '|'); ) 1244 | đường ống BAR_AND newline_list đường ống 1245 ( 1246 /* Tạo cmd1 |& cmd2 tương đương với cmd1 2>&1 | cmd2 */ 1247 COMMAND *tc; 1248 REDIRECTEE rd, sd; 1249 REDIRECT *r; 1250 1251 tc = $1->type == cm_simple ? (COMMAND *) $1->value.Simple: $1; 1252 rd.dest = 1; 1254 r = make_redirection (sd, r_duplicate_output, rd, 0 ) 1256 ( 1257 đăng ký REDIRECT *t; 1258 cho (t = tc->chuyển hướng; t->tiếp theo; t = t->tiếp theo) 1259 ; 1260 t->next = r; 1262 khác 1263 tc->redirects = r; '|'); lệnh 1268 ( $$ = $1; ) 1269 ; Cũng ở đây, chúng ta thấy quá trình xử lý cặp ký tự '|&', tương đương với việc chuyển hướng cả stdout và stderr vào đường dẫn. Tiếp theo, hãy xem command_connect():make_cmd.c :
194 LỆNH * 195 lệnh_connect (com1, com2, đầu nối) 196 LỆNH *com1, *com2; đầu nối int 197; 198 ( 199 KẾT NỐI *temp; 200 201 temp = (KẾT NỐI *)xmalloc (sizeof (KẾT NỐI)); 202 temp->connector = kết nối; 203 temp->first = com1; 204 temp->second = com2; 205 return ( make_command (cm_connection, (SIMPLE_COM *)temp)); trong đó trình kết nối là ký hiệu '|' dưới dạng int. Khi một chuỗi lệnh (được liên kết qua '&', '|', ';', v.v.) được thực thi, exec_connection():execute_cmd.c được gọi:
2325 trường hợp '|': ... 2331 exec_result = exec_pipeline (lệnh, không đồng bộ, pipe_in, pipe_out, fds_to_close);
PIPE_IN và PIPE_OUT là các bộ mô tả tệp chứa thông tin về luồng đầu vào và đầu ra. Chúng có thể lấy giá trị NO_PIPE, nghĩa là I/O là stdin/stdout.
exec_pipeline() là một hàm khá mở rộng, việc triển khai hàm này được chứa trong exec_cmd.c . Chúng tôi sẽ xem xét những phần thú vị nhất đối với chúng tôi.
exec_cmd.c :
2112 trước = pipe_in; 2113 cmd = lệnh; 2114 2115 while (cmd && cmd->type == cm_connection && 2116 cmd->value.Connection && cmd->value.Connection->connector == '|') 2117 ( 2118 /* Tạo một đường dẫn giữa hai lệnh */ 2119 if (ống (trường)< 0) 2120 { /* возвращаем ошибку */ } ....... /* Выполняем первую команду из конвейера, используя в качестве входных данных prev - вывод предыдущей команды, а в качестве выходных fildes - выходной файловый дескриптор, полученный в результате вызова pipe() */ 2178 execute_command_internal (cmd->value.Connection->first, không đồng bộ, 2179 prev, fildes, fd_bitmap); 2180 2181 if (trước >= 0) 2182 đóng (trước); 2183 2184 prev = phim; /* Đầu ra của chúng ta trở thành đầu vào cho lệnh tiếp theo */ 2185 close (fildes); ....... 2190 cmd = cmd->value.Connection->giây; /* “Di chuyển” tới lệnh tiếp theo từ đường ống */ 2191 ) Vì vậy bash xử lý ký hiệu đường ống bằng cách cuộc gọi hệ thống pipe() cho mỗi ký tự '|' gặp phải và thực thi từng lệnh trong một quy trình riêng biệt bằng cách sử dụng bộ mô tả tệp tương ứng làm luồng đầu vào và đầu ra.

Mã nguồn, cấp 2, cốt lõi

Hãy chuyển sang mã kernel và xem cách triển khai hàm pipe(). Bài viết này thảo luận về phiên bản kernel 3.10.10 ổn định.
(các phần mã không quan trọng đối với bài viết này sẽ bị bỏ qua):
/* Kích thước tối đa bộ đệm đường ống cho người dùng không có đặc quyền. Có thể được đặt bằng root trong tệp /proc/sys/fs/pipe-max-size */ 35 unsigned int pipe_max_size = 1048576; /* Kích cỡ nhỏ nhất Bộ đệm đường ống, theo khuyến nghị POSIX, bằng kích thước của một trang bộ nhớ, tức là. 4Kb */ 40 unsigned int pipe_min_size = PAGE_SIZE; 869 int create_pipe_files(struct file **res, int flags) 870 ( 871 int err; 872 struct inode *inode = get_pipe_inode(); 873 struct file *f; 874 đường dẫn đường dẫn cấu trúc; 875 static struct qstr name = (. name = “” ); /* Phân bổ nha khoa trong dcache */ 881 path.dentry = d_alloc_pseudo(pipe_mnt->mnt_sb, &name); /* Phân bổ và khởi tạo cấu trúc tệp. Hãy chú ý đến FMODE_WRITE, cũng như cờ O_WRONLY, tức là điều này. cấu trúc chỉ ghi và sẽ được sử dụng làm luồng đầu ra trong đường ống. Chúng tôi sẽ quay lại cờ O_NONBLOCK sau */ 889 f = alloc_file(&path, FMODE_WRITE, &pipefifo_fops); /* Tương tự, chọn và khởi tạo cấu trúc tệp để đọc (xem FMODE_READ và cờ O_RDONLY) */ 896 res = alloc_file(&path, FMODE_READ, &pipefifo_fops); 904 return 0; int *fd, struct file **files, int flag) 920 ( lỗi 921 int; 922 int fdw, fdr; /* Tạo cấu trúc tệp cho bộ mô tả tệp đường dẫn (xem chức năng ở trên) */ 927 error = create_pipe_files(files, flags); /* Chọn bộ mô tả tệp miễn phí */ 931 fdr = get_unused_fd_flags(flags); 936 fdw = get_unused_fd_flags(cờ); 941 kiểm toán_fd_pair(fdr, fdw); 942 fd = fdr; 943 fd = fdw; 944 trả về 0; 952 ) /* Thực hiện trực tiếp các hàm int pipe2(int pipefd, int flags)... */ 969 SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags) 970 ( 971 tệp cấu trúc *files; 972 int fd; / * Chúng tôi tạo cấu trúc cho đầu vào/đầu ra và tìm kiếm các bộ mô tả miễn phí */ 975 __do_pipe_flags(fd, files, flags); /* Sao chép các bộ mô tả tệp từ không gian kernel sang không gian người dùng */ 977 copy_to_user(fildes, fd, sizeof(fd)) ; Chúng tôi gán các bộ mô tả tệp cho các con trỏ tới các cấu trúc */ 984 fd_install(fd, files); 985 fd_install(fd, files); 989 ) /* ...và int pipe(int pipefd), về cơ bản là một trình bao bọc để gọi pipe2 với các cờ mặc định; */ 991 SYSCALL_DEFINE1(pipe, int __user *, fildes) 992 ( 993 return sys_pipe2(fildes, 0); 994 ) Nếu bạn nhận thấy, mã sẽ kiểm tra cờ O_NONBLOCK. Nó có thể được đặt bằng thao tác F_SETFL trong fcntl. Nó chịu trách nhiệm chuyển sang chế độ mà không chặn các luồng I/O trong đường ống. Ở chế độ này, thay vì chặn, quá trình đọc/ghi vào luồng sẽ kết thúc với mã lỗi EAGAIN.

Kích thước tối đa của khối dữ liệu sẽ được ghi vào đường dẫn bằng một trang bộ nhớ (4Kb) đối với kiến ​​trúc nhánh:
:
8 #define PIPE_BUF PAGE_SIZE Đối với hạt nhân >= 2.6.35, bạn có thể thay đổi kích thước bộ đệm đường ống:
fcntl(fd, F_SETPIPE_SZ, ) Kích thước bộ đệm tối đa được phép, như chúng ta đã thấy ở trên, được chỉ định trong tệp /proc/sys/fs/pipe-max-size.

Mẹo & thủ thuật

Trong các ví dụ bên dưới, chúng tôi sẽ thực thi ls trên thư mục Tài liệu hiện có và hai tệp không tồn tại: ./non-being_file và . /other_non-tồn tại_file.
  1. Chuyển hướng cả stdout và stderr sang pipe
    ls -d ./Documents ./không tồn tại_file ./other_non-tồn tại_file 2>&1 | egrep “Doc|other” ls: không thể truy cập ./other_non-exist_file: Không có tệp hoặc thư mục như vậy ./Documents hoặc bạn có thể sử dụng tổ hợp ký tự '|&' (bạn có thể tìm hiểu về nó từ tài liệu shell (man bash) hoặc và từ các nguồn ở trên, nơi chúng tôi đã phân tích cú pháp trình phân tích cú pháp bash Yacc):
    ls -d ./Documents ./non-being_file ./other_non-being_file |& egrep “Doc|other” ls: không thể truy cập ./other_non-being_file: Không có tập tin hoặc thư mục như vậy ./Documents
  2. Chuyển hướng _only_ stderr sang đường ống
    $ ls -d ./Documents ./không tồn tại_file ./other_non-tồn tại_file 2>&1 >/dev/null | egrep “Doc|other” ls: không thể truy cập ./other_non-being_file: Không có tập tin hoặc thư mục như vậy Tự bắn vào chân mình
    Điều quan trọng là phải giữ trật tự chuyển hướng stdout và stderr. Ví dụ: sự kết hợp '>/dev/null 2>&1' sẽ chuyển hướng cả stdout và stderr sang /dev/null.
  3. Lấy mã hoàn thành đường ống chính xác
    Theo mặc định, mã thoát đường ống là mã thoát của lệnh cuối cùng trong đường ống. Ví dụ: lấy lệnh ban đầu thoát ra với mã khác 0:
    $ ls -d ./không tồn tại_file 2>/dev/null; tiếng vang $? 2 Và đặt nó vào đường ống:
    $ ls -d ./không tồn tại_file 2>/dev/null | wc; tiếng vang $? 0 0 0 0 Bây giờ mã thoát đường ống là mã thoát lệnh wc, tức là. 0.

    Thông thường, chúng ta cần biết liệu có xảy ra lỗi trong quá trình thực hiện đường ống hay không. Để thực hiện việc này, hãy đặt tùy chọn pipefail, tùy chọn này cho shell biết rằng mã thoát đường ống sẽ khớp với mã thoát khác 0 đầu tiên của một trong các lệnh đường ống hoặc 0 nếu tất cả các lệnh hoàn thành chính xác:
    $ set -o pipefail $ ls -d ./non-exist_file 2>/dev/null | wc; tiếng vang $? 0 0 0 2 Tự bắn vào chân mình
    Bạn nên lưu ý đến các lệnh “vô hại” có thể trả về giá trị khác 0. Điều này không chỉ áp dụng khi làm việc với băng tải. Ví dụ: hãy xem xét ví dụ grep:
    $ egrep “^foo=+” ./config | awk '(print “new_”$0;)’ Ở đây chúng tôi in tất cả các dòng tìm thấy, thêm ‘new_’ vào đầu mỗi dòng hoặc không in gì nếu không có dòng định dạng bắt buộc không tìm thấy. Vấn đề là grep không thành công với mã 1 nếu không tìm thấy kết quả khớp nào, vì vậy nếu tập lệnh của chúng tôi có tùy chọn pipefail được đặt, ví dụ này sẽ thất bại với mã 1:
    $ set -o pipefail $ egrep “^foo=+” ./config | awk ‘(print “new_”$0;)’ >/dev/null; tiếng vang $? 1 Trong các tập lệnh lớn có cấu trúc phức tạp và quy trình dài, điểm này có thể bị bỏ qua và có thể dẫn đến kết quả không chính xác.

  4. Gán giá trị cho các biến trong một đường ống
    Trước tiên, hãy nhớ rằng tất cả các lệnh trong một đường ống được thực thi trong các quy trình riêng biệt thu được bằng cách gọi clone(). Điều này thường không phải là vấn đề trừ khi giá trị biến thay đổi.
    Hãy xem xét ví dụ sau:
    $ a=aaa $ b=bbb $ echo “một hai” | đọc a b Bây giờ chúng ta mong đợi giá trị của biến a và b lần lượt là “một” và “hai”. Trên thực tế, chúng sẽ vẫn là “aaa” và “bbb”. Nói chung, bất kỳ thay đổi nào đối với giá trị của các biến trong đường dẫn bên ngoài nó sẽ khiến các biến không thay đổi:
    $ filefound=0 $ tìm . -loại f -size +100k | while true do read f echo “$f is over 100KB” filefound=1 break # exit sau khi tìm thấy tệp đầu tiên $ echo $filefound; Ngay cả khi tìm thấy tệp lớn hơn 100Kb, cờ tìm thấy tệp vẫn sẽ được đặt thành 0.
    Có một số giải pháp khả thi cho vấn đề này:
    • sử dụng bộ -- $var
      Cấu trúc này sẽ đặt các biến vị trí theo nội dung của biến var. Ví dụ, như trong ví dụ đầu tiên ở trên:
      $ var=”one two” $ set -- $var $ a=$1 # “one” $ b=$2 # “two” Cần phải lưu ý rằng tập lệnh sẽ mất các tham số vị trí ban đầu mà nó được gọi .
    • chuyển tất cả logic để xử lý giá trị biến sang cùng một quy trình con trong đường ống:
      $ echo “một” | (đọc a; echo $a;) một
    • thay đổi logic để tránh gán các biến bên trong đường ống.
      Ví dụ: hãy thay đổi ví dụ tìm kiếm của chúng tôi:
      $ filefound=0 $ for f in $(find . -type f -size +100k) # chúng tôi đã xóa đường ống, thay thế nó bằng một vòng lặp do read f echo “$f is over 100KB” filefound=1 break done $ echo $ tìm thấy tập tin;
    • (chỉ dành cho bash-4.2 trở lên) sử dụng tùy chọn Lastpipe
      Tùy chọn Lastpipe hướng dẫn shell thực thi lệnh đường ống cuối cùng trong quy trình chính.
      $ (shopt -s Lastpipe; a=”aaa”; echo “one” | read a; echo $a) one Điều quan trọng là tùy chọn Lastpipe trên dòng lệnh phải được đặt trong cùng quy trình nơi đường ống tương ứng sẽ được đặt được gọi, vì vậy dấu ngoặc đơn trong ví dụ trên là bắt buộc. Trong tập lệnh, dấu ngoặc đơn là tùy chọn.