Dấu ngoặc nhọn trong shell

Dấu ngoặc nhọn trong shell
Photo by Snoy_My from Pixabay

Nhân bài viết trước viết về dấu ngoặc trong shell script, tôi sẽ tiếp tục những nội dung liên quan đến chủ đề đó. Trong bài viết này, tôi muốn nói đến một dấu ngoặc khác, dấu ngoặc nhọn {. Có thể có nhiều người đã sử dụng nó, nhưng không quá để ý (khi nó là một phần của cú pháp khác quan trọng hơn). Bài viết này sẽ tổng hợp lại các tình huống khác nhau mà dấu ngoặc nhọn { được sử dụng.

Khai triển

Cú pháp này rất đơn giản thôi, {a,b} (không có dấu cách) tương đường với a b:

$ echo {a,b}
a b

Cú pháp này có thể được vận dụng trong nhiều trường hợp giúp câu lệnh đơn giản, ngắn gọn hơn:

  • mv path/{old,new}.py thay thế cho mv path/old.py path/new.py
  • ls *{txt,log} sẽ liệt kê tất cả các file có phần mở rộng là txt hoặc log
  • cp path/{half_,full_}adder.v . copy half_adder.vfull_adder.v
  • mv story{,_old}.txt đổi tên story.txt thành story_old.txt
  • touch file_{x..z}.txt tương đương với touch file_x.txt file_y.txt file_z.txt (sẽ nói kỹ hơn ở phần sau)

Tạo chuỗi

Nhân vật chính của cú pháp này là hai dấu chấm ... Tuy nhiên, dấu ngoặc nhọn cũng là một nhân vật quan trọng.

Thông thường, một chuỗi các số từ 0~10 có thể được tạo ra thế này:

$ echo {0..10}
0 1 2 3 4 5 6 7 8 9 10

Vẫn là những số đó nhưng thứ tự ngược lại:

$ echo {10..0}
10 9 8 7 6 5 4 3 2 1 0

Nếu chỉ muốn in ra các số chẵn (hoặc lẻ) thì sử dụng cú pháp này. Đây là cú pháp in ra các số cách nhau 2 đơn vị kể từ 10 giảm về 0:

$ echo {10..0..2}
10 8 6 4 2 0

Array có thể bao gồm cả ký tự chứ không phải chỉ có số:

$ echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z

Cú pháp này sẽ in ra tất cả các chữ cái tiếng Anh từ a đến z. Còn rất nhiều cách tạo array khác nữa với cú pháp tương tự.

Hai hoặc nhiều chuỗi có thể kết hợp với nhau để tạo ra một tổ hợp, ví dụ sau sẽ ra tổ hợp 2 ký tự từ aa đến zz.

$ echo {a..z}{a..z}
aa ab ac ad ...

Trong cú pháp của shell (như bash hay zsh), có thể định nghĩa array sử dụng dấu ngoặc tròn (), các giá trị của array sẽ ngăn cách bằng dấu cách:

$ month=(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)

Để truy cập đến phần tử của array, có thể sử dụng index:

$ echo ${month[3]}
Mar

Kết hợp việc tạo các chuỗi ở trên, có thể tạo array nhanh hơn bằng cách:

$ letter_combos=({a..z}{a..z})

Lúc này, letter_combos sẽ là biến lưu giá trị array chứa các tổ hợp 2 ký tự từ aa đến zz. Có thể dùng cách này để tạo một array như sau:

$ dec2bin=({0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1})

Array dec2bin sẽ chứa toàn bộ các số nhị phân 1 byte sắp xếp theo thứ tự tăng dần. Bằng việc sử dụng array này, có thể dễ dàng chuyển đổi các số thập phân (trong khoảng 0~255) thành nhị phân. Lưu ý rằng, mặc định zsh dùng index bắt đầu từ 1 (áp dụng với array tháng ở trên thì chuẩn).

$ echo ${dec2bin[@]:25:1}
00011001
$ echo ${dec2bin[26]}
00011001

Khai triển biến

Thực ra cú pháp khai triển biến đã có từ phần trước:

$ echo ${month[3]}
Mar

Trong cú pháp này, thực ra dấu ngoặc nhọn là một phần, phần còn lại là dấu $ tạo thành cú pháp ${...}. Có thể dùng cú pháp này để gọi một biến thông thường, gọi giá trị array theo index, và nhiều cách dùng khác nữa.

Một trong những cách “khác” là cắt bỏ phần đuôi thừa của biến. Ví dụ:

$ a='Too longgg'
$ echo ${a%gg}
Too long

Trong trường hợp này, ngoài cú pháp khai triển biến thông thường, chúng ta phải thêm vào dấu % và giá trị mà chúng ta muốn cắt bỏ. Đây là cú pháp rất hữu ích khi cần chuyển đổi tên file từ định dạng này sang định dạng khác.

Ví dụ, tôi thường sử dụng ImageMagick (với lệnh convert) chuyển đổi định dạng các file ảnh để giảm bớt dung lượng. Để chuyển một file từ PNG sang WEBP thì đơn giản như sau:

$ convert image.png image.webp

Tuy nhiên, khi cần chuyển đổi một thư mục có vài chục đến hàng trăm ảnh khác nhau, thì việc viết tên file như lệnh trên gần như là không thể. Lúc đó, cú pháp khai triển biến ở trên sẽ rất hữu ích:

$ for f in *.jpg
for> do
for> convert $i ${i%jpg}webp
for> done

Không chỉ cắt bỏ phần đuôi thừa, cú pháp khai triển biến còn có thể cắt bỏ phần đầu với cú pháp dùng dấu # thay cho %:

$ a='Hello World!'
$ echo Goodbye${a#Hello}
Goodbye World!

Còn nhiều trường hợp khác có thể sử dụng cú pháp khai triển biến nữa. Đặc biệt, khi viết script để tự động hóa công việc, cú pháp này sẽ đóng một vai trò quan trọng.

Nhóm các lệnh với nhau

Dấu ngoặc nhọn còn có thể được sử dụng để nhóm một số lệnh với nhau tạo thành một cụm lệnh lớn. Ví dụ:

$ echo "I found all these PNGs:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls > PNGs.txt

Nếu chạy lệnh này, cú pháp chuyển hướng chỉ áp dụng với câu lệnh cuối cùng ls. Đó không phải là điều tôi mong muốn, tôi muốn toàn bộ các lệnh này sẽ chuyển hướng kết quả đến file PNGs.txt. Lúc này, thay vì chuyển hướng cho từng lệnh (dùng >> để ghi thêm nội dung), tôi chỉ đơn giản nhóm chúng lại với nhau là xong:

$ { echo "I found all these PNGs:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls; } > PNGs.txt

Lưu ý là cú pháp này bắt buộc phải có dấu cách sau { và trước }, đồng thời các lệnh ngăn cách với nhau bằng dấu chấm phẩy ;.

Tôi xin lỗi nếu bài viết có bất kỳ typo nào. Nếu bạn nhận thấy điều gì bất thường, xin hãy cho tôi biết.

Nếu có bất điều gì muốn nói, bạn có thể liên hệ với tôi qua các mạng xã hội, tạo discussion hoặc report issue trên Github.