Share This

Mở rộng phạm vi của năng lực Shell

23/08/2017
  • Chức năng mới của bash, rất tiện lợi.
  • Tại bài viết này sẽ giới thiệu 1 số chức năng mới được thêm vào các version từ 4.0 trở đi.
  • Nếu cân nhắc về những khía cạnh như là tính tương thích của shellscript thì cần phải thận trọng trong việc thêm vào chức năng mới, đành rằng là không sử dụng nhưng nên biết thì tốt hơn.

Chúng ta hãy cùng vừa xem vừa suy nghĩ những chức năng này được thêm vào để giải quyết những vấn đề gì.

Tháng 9/2016, version của bash đã tăng lên thành 4.4, đồng thời cũng đã thêm vào trong version đó nhiều chức năng mới. Tuy nhiên, lại thiếu đi tự tin, hứng thú khi mỗi lần version up bash. Lý do là, ví dụ khi muốn cho những shell script đã viết phù hợp với chuẩn POXIS thì chỉ toàn thấy những lỗ hổng nguy hiểm của những chức năng mới của bash (*1). Ngược lại, ngay cả khi kết hợp câu lệnh nhằm thực hiện shell, thì phần lớn những chức năng được sử dụng là pipe và redirect.
Cảm nhận được có vẻ như đại loại là các trạng thái cảm xúc (tình trạng) như là「Đã thêm chức năng mới vào bash rồi!」「ồ..」

Mặc dù nói rằng: có lẽ việc thêm chức năng mới thì có lý do riêng của nó nhưng một trong những lý do đó là có người cho rằng việc thêm chức năng mới là cần thiết và chấp thuận việc thêm vào đó.

Thêm nữa, tìm thấy một số chức năng có thể đáp ứng mục tiêu thiết lập không cho user thông thường nhìn thấy việc quản lý các loại dữ liệu và flag bên trong bash bằng cú pháp và cơ cấu của bash. Cho dù không sử dụng những chức năng thừa, nhưng nhờ vào việc nắm bắt điều đó giúp hình dung ra cơ cấu của Shell. Vì vậy, mục đích của bài viết này là, tập trung vào việc thêm chức năng mới vào bash version từ 4.0 trở đi, đào sâu giúp độc giả nắm bắt được các quan điểm như là 「Tại sao lại có chức năng như vậy?」「Tiện ích là gì?」. Giống như đã đề cập lúc nãy, đọc giả thì không lưu tâm lắm đến chức năng chi tiết của bash, nhưng mặc dù vậy, vì vẫn có những suy nghĩ rằng bash thì bất tiện nên muốn đề cập đến cách làm sao để giải quyết vấn đề đó khi thông qua các chức năng mới.

Chuẩn bị cho bash 4.4

Vì code của bash thì có tại https://ftp.gnu.org/gnu/bash/, nên hãy download version 4.4 về và sử dụng. Tại phần lớn môi trường OS dành cho UNIX, có thể cài đặt được bằng tay theo chỉ dẫn 1. Vì những version cũ khác thì cũng có thể setup giống vậy, nên trong trường hợp mà tự so sánh version cũ mới, thì thử thay đổi chỉ dẫn 1 của file tar.gz rồi install 1 số bash. Tác giả đã xác minh môi trường sử dụng tại bài viết này là Ubuntu 16.04 Server.
Có những thay đổi gì tại các version liên tiếp được ghi chép lại tại file CHANGES nằm trong đường dẫn đã được tạo ra bằng tar. Nếu có hứng thú, xin hãy nghiên cứu.

Thêm nữa, xin hãy thường xuyên kiểm tra biến BASH_VERSION khi vừa chuyển đổi version vừa thực hiện công việc. Ví dụ: Nếu đặt version 4.4 và 3.2 tại path theo trình tự của chỉ dẫn 1, thì có thể chuyển đổi version tiếp theo theo cách như vậy.

$ bash4.4
$ echo $BASH_VERSION
$ 4.4.0(1)-release
Trường hợp tạo và đặt version 3.2 theo chỉ dẫn 1
$ bash3.2
Mặc dù sẽ xuất hiện warning do có liên quan đến associative array (đề cập sau), nhưng không cần phải lưu tâm, chú ý.
$ echo $BASH_VERSION
$ 3.2.57(1)-release

Tổng hợp Standard Output và Standard Error Output và có thể xuất ra pipe「|&」

Trước hết sẽ đi từ chủ đề đơn giản. Từ version 4.0 trở đi thì |& đã trở thành ký hiệu của pipe. Khi sử dụng bash, thỉnh thoảng sau khi tổng hợp Standard Output và Standard Error Output, muốn truyền đi command khác nhưng cho đến version 3.2 thì trong trường hợp này:

$ ls a aa 2>&1 l nl
1 ls : không thể truy xuất đến 'aa': giản lược như dưới đây
2 a

Đã cần viết lại phần thuyết minh cho thao tác của file descriptor 2>&1.
Ý nghĩa của One-Liner là số hàng được đính bằng command nl sau khi tổng hợp Standard Output và Standard Error Output của Is tại 2>&1 rồi truyền đến pipe. Đầu tiên là Error Output của Is, tiếp theo là Standard Error Output của Is đi qua pipe, số thứ tự sẽ được đính bằng nl, và sẽ thành Output như vầy. Về file descriptor thì chỉ là một tính chất nhỏ trong bài viết này nên phần thuyết minh, giải thích sẽ bị giản lược.
Kể từ version 4.0 trở đi, không cần nghĩ đến những khó khăn nhỏ đó mà bằng:

$ ls a aa l& ln
1 ls : không thể truy xuất đến 'aa': giản lược phần còn lại
2 a

Vẫn có thể hoàn tất công việc. Thế nhưng, vì chỉ giảm số lượng chữ số thì không mang ý nghĩa thêm chức năng mới.
Cái nào thì thuận tiện? Sẽ đưa ra 1 ví dụ về case sắp tới. Ví dụ: Khi thêm find vào Directory đối với các user chung như dưới đây, thì sẽ xảy ra lỗi tại path của file hoặc directory tại Standard Output và Standard Error Output.

$ find /proc/
/proc/
/proc/ fb
(..giản lược..)
find: `/proc/tty/driver': không có authorization
find: `/proc/1/task/1/fd': không có authorization
(..giản lược..)

▼Chỉ dẫn 1: install bash
$ wget https://ftp.gnu.org/gnu/bash/bash-4.4.tar.gz
$ tar zxvf bash-4.4.tar.gz
$ cd bash-4.4/
$ ./configure && make -j
$ sudo cp bash /usr/local/bin/bash4.4
$ bash4.4 --version
GNU bash, version 4.4.0(1)-release (x86_64-unknow-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
Licence GPLv3+: GNU GPL version 3 hoặc hơn nữa <https://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Nếu không thêm find thì khi kết hợp 2 loại Output và xem bằng thiết bị đầu cuối (terminal) thì:

$ find /proc/ 2> /dev/null
↑ Chỉ xem Standard Output
$ find /proc/ > /dev/null
↑ Chỉ xem Error

Hoặc là:

$ find /proc/ > a 2> b

Làm như trên và gom lại 1 lần trong file rồi xem.
Trường hợp như vậy thì việc sử dụng |& sẽ trở nên thuận tiện hơn. Ví dụ: khi mà kết hợp các command khác hoặc less như bước tiếp theo:

$ find /proc l& less
↑ Xem cả 2
$ find /proc l& grep ^find: | less
↑ Chỉ xem Error
$ find /proc l& grep -v ^find: | less
↑ Chỉ xem Standard Output

Thì sẽ không cần tạo file rác a hoặc b mà vẫn có thể chọn xem riêng các loại Output.
Bởi vì không có |& nên có vẻ sẽ không có những vấn đề khó khăn xảy ra, nhưng nếu trở thành phản xạ, thì tác giả đang suy nghĩ rằng người sử dụng sẽ sử dụng với tần suất nhiều hơn hay không? Có 1 điểm lưu ý là, thứ tự truyền Standard Output và Standard Error Output đến Pipe thì không được quy định.

Giới thiệu ký hiệu có liên quan &>> , dùng để tổng hợp Standard Output và Standard Error Output rồi ghi thêm vào file.

$ ls a b &> result
↑ Tổng hợp cả 2 loại Output rồi Redirect đến file
$ ls a b &>> result
↑ Tổng hợp cả 2 loại Output rồi ghi thêm vào file
$ cat result
ls: Không thể truy xuất đến 'a': giản lược phần còn lại
b
ls: Không thể truy xuất đến 'a': giản lược phần còn lại
b

(*1) Có thể nói là viết bằng sh nhưng nếu giả lập sh, vì có môi trường chạy bash nên tình hình thì không đơn giản như vậy.

$ ls a b &> result
↑ Tổng hợp 2 output rồi lưu vào file

$ ls a b &>> result
↑ Tổng hợp 2 output rồi cộng thêm vào file

$ cat result
ls: không thể truy cập 'a': đoạn sau lược bỏ
b
ls: không thê truy cập 'a': đoạn sau lược bỏ
b

「★★」 —- globstar

Có lệnh nội trú shopt dùng cho bash, có tác dụng enable hoặc disable rất nhiều tính năng khác nhau. Giống như sau đây:

$ shopt -s globstar

Khi thực hiện như trên, tính năng globstar trở nên có thể sử dụng.
Về cách sử dụng. Ví dụ: chúng ta cân nhắc trường hợp có một thư mục chứa thư mục và file khác như sau (Xử dụng xargs để tiết kiệm không gian trình bày trên tạp chí).

$ find | xargs
. ./c ./a ./b ./b/f ./b/e ./b/h ./b/h/i ↩
./b/h/j ./b/g

Nếu dùng lệnh echo ★★ thư mục này ra thì:

$ echo ★★
a b b/e b/f b/g b/h b/h/i b/h/j c

Chúng ta có thể thấy cấu trúc thư mục giống như khi sử dụng lệnh find và có kết quả như trên.
(Lưu ý: những thư mục và file mà bắt đầu bằng 「.」 thì không thể được output ra)
★★ là kí hiệu của globstar.

Ngoài ra, khi trích xuất chỉ riêng thư mục mà thôi, nếu dùng find thì:

$ find -type d
.
./c
./b
./b/h

Cần phải set các option giống như trên. Mặt khác nếu viết: ★★/ thì cũng có thể trích xuất được thông tin giống vậy.

$ echo ★★/
/b b/h/ c/

Tiếp nữa, nếu kết hợp như sau, có thể trích xuất toàn bộ cách file có đuôi mở rộng là conf nằm trong tất cả thư mục con, cháu của thư mục /etc/.

$ echo /etc/★★/★.conf | tr '''\n' | enter
head -n 3
/etc/adduser.conf
/etc/apache2/apache2.conf
/etc/apache2/conf-available/charset.conf

Khi sử dụng chức năng này thường trú trên device thì cần phải viết sẵn như sau trong .bashrc và bash_profile:

shopt -s globstar

Bên cạnh đó, khi chỉ muốn sử dụng chỉ những lúc cần thì thực hiện lệnh sau trên thiết bị:

shopt -s globstar .

Nếu muốn tắt chức năng này thì chỉ định option -u thay vì -s

Có một điểm cần chú ý, khác với lệnh find, vì lệnh này đã sort sẵn luôn thứ tự hiển thị nên khi sử dụng đối với trường hợp thư mục có càng nhiều file thì có khả năng càng chậm. Đối với tính toán về ouput sau đây, tác giả đã thực hiện đo đạc thời gian thực hiện find và ★★ đối với thư mục được tạo từ ví dụ trên và toàn bộ file system, và kiểm chứng trên môi trường Ubuntu 16.04 Server.

Tác giả đo rất nhiều lần, ở trạng thái sử dụng cache.

Thư mục có số lượng file ít.

$ time find ./ &> /dev/null

real 0m0.007s
user 0m0.000s
sys 0m0.004s

$ time echo ** &> /dev/null

real 0m0.001s
user 0m0.000s
sys 0m0.000s

Thư mục có số lượng file nhiều:

$ time find ./ &> /dev/null

real 0m0.963s
user 0m0.368s
sys 0m0.588s

$ time echo /** &> /dev/null

real 0m7.291s
user 0m6.092s
sys 0m1.192s

Giống như đã nêu trên khi số lượng file càng nhiều thì thời gian thực hiện lệnh find và ** sẽ đổi chiều. Khi số lượng file ít, việc lệnh find thực hiện chậm được dự đoán do nó là lệnh ngoại trú, còn lệnh echo nhanh hơn là vì nó là lệnh trú.

Các tính năng khác có thể setting bằng shopt

Nhân đây, tác giả giới thiệu luôn lệnh failglob và autocd có liên quan với shopt. Ngoài ra có rất nhiều tính năng khác nữa, nếu ai muốn tìm hiểu sâu thêm, vui lòng dùng man bash để tham khảo thêm các đặc tả liên quan đến shopt.

failglob là chức năng sẵn có từ phiên bản ver 3.0 trở về sau.

$ touch *.txt

Khi muốn tiến hành các thao tác sử dụng thời gian hiện tại đối với timestamp của các file có đuôi mở rộng là txt thì thực hiện như trên. Tuy nhiên trường hợp nếu trong thư mục hiện tại mà không có file giống như vậy, thì nó sẽ tự động tạo ra một file 「*.txt」 giống như sau:

$ touch *.txt
$ ls
*.txt

Nếu như thế thì vừa phiền phức, trường hợp nếu tạo các file có kí tự đặc biệt vào trong thư mục, cũng có khi lại có những tạo ra những rủi ro tiềm ẩn khác.

Trường hợp muốn phòng tránh điều đó, nên mô tả sẵn trong file .bashrc như sau:

shopt -s failglob

Thì sẽ xuất hiện lỗi như sau:

$ touch *.txt
-bash: file *.txt không phù hợp

Về mặt cá nhân tác giả nghĩ rằng khi sử dụng option này sẽ khá tiện lợi, nhưng đáng tiếc có vẻ cũng sẽ có những trường hợp tab autocomplete và tab chen ngang.

Autocd là tính năng dù loại bỏ cd, chỉ cần nhập tên thư mục vào terminal thì vẫn cd được, hãy xem ví dụ sau:

$ shopt -s autocd
$ /etc/
cd /etc/
$ ~
cd /home/ueda  → tương ứng user hiện tại

Có thể khi bạn quen sẽ tiện lợi, tuy nhiên khi chuyển qua môi trường khác không sử dụng tính năng này có thể dễ lẫn lộn (loạn).

Mảng liên tưởng, đọc dữ liệu vào một mảng

Chúng ta có thể sử dụng mảng liên tưởng từ version 4.0 trở về sau. Ví dụ mình họa về cách sử dụng như hình số 2.

Hình 2: cách sử dụng mảng liên tưởng
$ declare -A tel
$ tel[police]=113 ← tạo mảng liên tưởng tel
$ tel[cuu_hoa]=114 ← gán giá trị 113 cho key police
$ echo ${tel[police]} ← gán giá trị 114 cho key cuu_hoa
113
$ echo ${tel[time]} ← Gọi ra giá trị của police
$ ← không output ra gì cả
$$ echo ${!tel[@]}a ← liệt kê key
police cuu_hoa

Vì bình thường tác giả hầu như không sử dụng tính năng này, nên rất khó khăn để đưa ra một ví dụ thích hợp cho việc nên sử dụng như thế nào, tuy nhiên vẫn xin đưa ra 1 ví dụ cho việc sử dụng.

Việc xác nhận thực hiện trên môi trường Ubuntu Server 16.04 và 17.04, sau khi gõ lệnh:
$ set -x

Khi tiến hành vừa load lại setting như sau và vừa search declare bẳng |& đã nhớ như ban nãy thì có thể output ra như sau.

$ source ~/.bashrc |& grep declare
+ grep --color=auto declare
+ source /home/ueda/.bashrc
+++ declare -A _xspecs

Khi xem dòng cuối cùng chúng ta thấy mảng liên tưởng -xspecs được tạo ra. Nhân tiện mảng liên tưởng này được nạp dữ liệu vào nhằm mục đích autocomplete.

$ echo ${_xspecs[latex]}
!*.@(?(la)tex|texi|dtx|ins|ltx|dbj)

Ở ví dụ này, đuôi mở rộng liên quan đến lệnh latex được nạp vào.

Trường hợp muốn lấy ra thông tin ngay lập tức đối với mảng kí tự được nhập vào như thế này, mảng liên tưởng là phù hợp nhất.
(Những ai quan tâm đến chức năng auto complete này hãy dùng keyword bash-completion để mà điều tra tìm hiểu thêm) . Ngoài ra, bởi vì data autocomplete được quản lý bằng cú pháp của bash, nên người sử dụng bash cũng có thể tùy chỉnh được.

Mặt khác mà nói, về phương diện cá nhân không khuyến khích việc sử dụng mảng liên tưởng một cách mù quáng. Trường hợp chỉ dẫn cái gì cho user ở phần response hoặc quản lý flag có lẽ nên chú ý giống như trường hợp ví dụ _xspecs. Đối với xử lý để tạo data, cá nhân chủ trương làm theo hướng sử dụng file sẽ hiệu quả hơn.

Ví dụ, trường hợp nếu đưa ví dụ về mảng liên tưởng cho người mới bắt đầu, dễ đưa luôn ví dụ như police, cuu_hoa ở hình số 2.

for key in ${!tel[@]} ; do
#$key và ${tel[$key]} # ví dụ xử lý sử dụng như ở đây
done

Cái này là một ví dụ điển hình cho sự không được phù hợp lắm. Nếu là một ngôn ngữ thông thường có thể ổn, nhưng nếu cho người mới bắt đầu xem luôn, thì sẽ mất luôn sự tưởng tượng đến việc sử dụng pipeline. Thêm nữa, cái này cũng khác với các ngôn ngữ thông thường, nó vừa có nhiều ngoặc, các kí số tùm lum, nên tạo ra một ấn tượng cứng nhắc, đáng ngại. Việc sử dụng mảng liên tưởng những lúc chẳng đặng đừng, về cá nhân cẳm nhận khá rối rắm, nên việc giới thiệu về mảng liên tưởng xin được tạm dừng tại đây.

Thêm một cái nữa, cái này cũng cấm lạm dụng: chức năng đọc dữ liệu từ file ra mảng đã được sử dụng từ version từ 4.0 trở đi. Xin đưa ra ví dụ, giống như sau đây sử dụng lệnh mapfile.

$ mapfile -t passwd < /etc/passwd
$ echo ${passwd[0]}
root:x:0:0:root:/root:/bin/bash
$ echo ${passwd[-1]}
ueda:x:1001:1001:ueda,,,:/home/ueda:/ enter
bin/bash

Ví dụ này copy nội dung của /etc/passwd cho mảng [passwd], rồi gọi lệnh echo để in ra. Option-t của mapfile mang ý nghĩa xuống dòng. Bên cạnh đó, chỉ định index âm cho mảng giống như list của python, phương pháp này giúp tham chiếu những phần tử của mảng từ đằng sau. Đây cũng là chức năng được thêm vô kể từ version 4.3.

Lệnh mapfile tiện cho khi cần quản lý các tham số nhỏ lấy ra từ bash. Tuy nhiên, trừ khi sử dụng nó là điều bất đắc dĩ khi không có lệnh nào bên ngoài có thể sử dụng, mà tôi nghĩ rằng không nên dùng nó để xử lý dữ liệu trong câu lệnh for.

CO-PROCESS

Tiếp đến, tôi xin giới thiệu về Co-process đã có từ phiên bản 4.0. Đây là nội dung hại não nhất trong tất cả các mục được giới thiệu trong tập san này. Trước hết là một ví dụ.

$ coproc awk '{print $1*2;fflush()}'
[1] 10872
$ seq 1 3 >&"${COPROC[1]}"
$ read n <&"${COPROC[0]}" ; echo $n
2
$ read n <&"${COPROC[0]}" ; echo $n
4
$ read n <&"${COPROC[0]}" ; echo $n
6

Trong ví dụ này, ở dòng đầu tiên tôi đã dùng code awk để truyền vào lệnh coproc. Code awk này nạp và đọc text chứa các chữ số input ở dòng thứ nhất, và nhân với 2 rồi xuất ra ngõ standard output. Hàm fflush dùng để xuất các dữ liệu từ output buffer sang standard output, khi mỗi dòng lệnh hoàn thành thì các kết quả sẽ được đẩy thêm vào standard output. Ở dòng lệnh thứ 3, sẽ in ra 1, 2, 3 nằm trên 3 dòng bằng lệnh seq (sequence), sau đó được chuyển hướng bằng >&”${COPROC[1]}”. Lúc này trong mảng COPROC sẽ có các con số như sau.

$ echo ${COPROC[@]}
63 60

Đây là mảng chứa 2 phần tử 63 và 60. Do đó, lúc thực thi >&”${COPROC[1]}” đã trở thành >&60, các kết quả xuất ra sẽ trỏ đến con trỏ của file descriptor thứ 60.

Lệnh read n <&63 ở dòng thứ 4, 6, 8 đọc kí tự từ con trỏ của file descriptor thứ 63 gán vào biến n, rồi xuất ra bằng lệnh echo $n. Kết quả xuất ra giống như dòng thứ 5, 7, 9, là các số đã xuất ra từ lệnh seq sau khi được nhân thêm 2. Nói cho vuông, đó là việc xử lý một chương trình được chỉ định bằng awk và trả về kết quả.

Điều đó có nghĩa là lệnh awk đã viết ở dòng thứ nhất có vẻ như đang chạy ở một nơi khác. Process ID của awk sẽ được lưu trong biến COPROC_PID, và có thể stop theo cách này:

$ kill -KILL $COPROC_PID
[1]+ force quit coproc COPROC awl '{print $1*2;fflush()}'

Đó là cách dễ nhất để có thể sử dụng co-process, và có lẽ ấn tượng ban đầu của bạn về nó đại loại như [Cái giống gì thế này…]. Nói ngắn gọn:

  • Các lệnh đã chỉ định có thể được khởi chạy từ coproc.
  • Các input/output của command sẽ được quản lý thông qua các biến coproc.
  • Các lệnh đã được khởi chạy có thể đọc ghi bất cứ lúc nào.

Như vậy, có thể nói nó là chức năng server hoá các dòng lệnh.

Lòng vòng vậy thì khó hình dung, tôi sẽ chỉ ra một ví dụ thực tế. Trong List 1 là một chương trình tìm ước số chung lớn nhất được viết bằng bash và awk. Dĩ nhiên là có thể viết hoàn toàn bằng bash hoặc bằng awk, nhưng trong ví dụ này sử dụng cả hai. Trong ví dụ này, điều quan trọng không phải nội dung của thuật toán, mà là việc gọi lặp lại các hàm sub bên trong vòng while loop của bash. Lệnh awk đã cài trong hàm sub hiện thực việc hễ nhập 2 số vào thì sẽ xuất ra 2 số khác, và kết quả xuất ra được nhập lại vào awk bằng bash. Bên cạnh đó, set — ở dòng thứ 7 là cách khai báo để gán các giá trị của arguments (là các parameters có trình tự của bash) $1, $2… trong chuỗi được ghi ở phía sau. Trong trường hợp này, output của lệnh $() được đặt vào 2 con số gán ở $1 và $2.

List 1: gcd.no_coproc.bash
function sub() {
awk '$1>$2{print $1%$2,$2;fflush()}
$1<=$2{print $2%$1,$1;fflush()}' } while [ "" -ne 0 ] ; do set -- $(echo $1 $2 | sub) echo ">" $1 $2 >$2
done
echo "$(($1+$2))"

Trong trường hợp chạy một chương trình lấy kết quả xuất của một dòng lệnh để làm input của chính nó lần nữa như thế này, về cơ bản chúng ta đang thực hiện một vòng lặp while bên trong shell script. Khi đó, khi số lần vòng loop nhiều thì thời gian khởi chạy lệnh (chính là thời gian để khởi chạy awk) cũng tiêu tốn, không thể bỏ qua.

Nếu chương trình này được sửa lại sử dụng co-process như trong List 2, vấn đề này về cơ bản sẽ biến mất. Bạn có thể đặt tên cho co-process, và nếu như viết giống dòng 1 đến 4 trong List 2, bạn cũng có thể viết chỉ thị tương tự như một function.

List 2 gcd.coproc.bash
coproc sub {
awk '$1>$2{print $1%$2,$2;fflush()}
$1<=$2{print $2%$1,$1;fflush()}' } a="$1" b="$2" while [ $((a*b)) -ne 0 ] ; do echo $a $b >&"${sub[1]}"
read a b <&"${sub[0]}" echo ">" $a $b >&2
done
echo $((a+b))

Giờ thì cùng thử so sánh số lượng phép tính nhé. Hãy thử bật lệnh time và chạy thử như trong hình 3. Khi so sánh mục real (tổng thời gian thực sự chạy) với nhau, thời gian thực hiện bằng co-process đã thấp hơn 4 lần (0.008s vs 0.036s). Trong ví dụ này, số vòng lặp loop chỉ có 9 lần nhưng nếu vòng lặp loop nhiều hơn thì thời gian xử lý cũng sẽ tăng lên theo.

Hình 3 – So sánh số tính toán
$ time bash ./gcd.no_coproc.bash 10710 102012
> 5622 10710
> 6 12
> 0 6
6
real 0m0.036s

$ time bash ./gcd.coproc.bash 10710 102012
6
real 0m0.008s

Đến đây là kết thúc của nội dung về co-process. Kết luận lại, tôi nghĩ “Lập trình với co-process thì khó, nhưng khi gọi cùng câu lệnh nhiều lần bằng vòng loop thì cũng có thể cân nhắc để sử dụng “. Tuy nhiên, trừ việc output của co-process luôn luôn có thể đọc được, khi đọc dữ liệu bằng lệnh read thì chương trình sẽ tắt ngúm luôn. Điều này cần được chú ý.

Phụ lục – pilefail

Pipefail là một tính năng có từ version 3.0 đến nay, tuy nhiên nó là một phần nhỏ nằm ngoài nên tôi sẽ giới thiệu thêm ở đây. Pipefail là cài đặt dùng để dừng một script khi có lỗi xuất hiện bên trong pipe. Mặc dù nó khá giống với việc liên quan tới shell script là “bật tuỳ chọn -e trong sh hay bash, nếu có lỗi xảy ra sẽ dừng luôn”, nhưng khá là nhập nhằng.

List 3 set_e.bash
set -e
false | true
echo "NG"

List 4 set_e_pipefail.bash
set -e
set -o pipefail
false | true
echo "NG"

Ví dụ List 3 thiết đặt option-e ở dòng thứ 1, false ở dòng thứ 2 sẽ văng lỗi. Tuy nhiên khi chạy thì nó không ngừng tại dòng 2 mà hiện ra như sau.

$ bash set_e.bash
NG

Tức là dòng thứ 3 sẽ được chạy luôn. Để ngừng lại ngay dòng false, viết như dòng thứ 2 trong List 4:

set -o pipefail

Khi chạy thì kết quả như dưới đây.

$bash set_e_pipefail.bash
(không có gì in ra hết)
$

RELATED POST

See more






    Share
    Close