Sử dụng tính năng mở rộng (Expansion) của Bash Shell Image Sử dụng tính năng mở rộng (Expansion) của Bash Shell

Bài viết này giới thiệu về cách sử dụng tính năng mở rộng (Expansion) của Bash Shell trên hệ điều hành Linux qua các định nghĩa và cách sử dụng dưới đây giúp cho các bạn có thể tự tìm hiểu và tự học Linux cơ bản dễ dàng hơn.

1. Giới thiệu về shell expansion

Giới thiệu về shell expansion

Khi chúng ta thực hiện gõ một lệnh (command) và nhấn phím enter thì bash sẽ thực hiện một số xử lý trên văn bản trước khi thực thi lệnh của chúng ta vừa gõ. Ví dụ như * sẽ có rất nhiều ý nghĩa đối với shell. Quá trình thực hiện điều này được gọi là mở rộng (expansion). Việc mở rộng mô tả như sau khi chúng ta thực hiện gõ một thứ gì đó và nó được mở rộng thành một thứ khác trước khi shell tác động lên nó.

Để chứng minh điều nay chúng ta sử dụng lệnh echo thực hiện in ra các đối số văn bản của nó trên đầu ra tiêu chuẩn:

[root@localhost ~]# echo "This is shell expansion"
This is shell expansion

Bất kỳ đối số được truyền cho echo thì đều được hiển thị. Sau đây chúng ta thực hiện một ví dụ khác về lệnh echo mở rộng như sau:

Khi chúng ta gõ echo * thì bash sẽ xử lý * trước khi thực hiện lệnh echo.

[root@localhost ~]# echo *
3 anaconda-ks.cfg ddrescue-1.17-1.el7.rf.x86_64.rpm descriptor descriptor2 descriptor3 Desktop Documents estpipe file file2.txt index.html Mail Music my_pipe netowrk.txt pcodein.pl pcode.pipe Public raid1 raid10 raid1.1. raid5 raid6 rpipe.sh sd.sh test6.txt test.txt Videos vidu1.txt vidu.txt

Dấu * là một ký tự đại diện bất kỳ ký tự nào trong tên tệp. Qua đây chúng ta thấy shell sẽ mở rộng * thành một thứ khác (trong trường hợp này là tên của các tệp trong thư mục hiện tại) trước khi lệnh echo được thực thi. Khi nhấn phím enter, shell sẽ tự động mở rộng bất kỳ ký tự đủ điều kiện nào trên dòng lệnh trước khi lệnh được thực hiện.

2. Cách thức hoạt động của shell expansion

Mở rộng (expansion) là một cơ chế mà các chuỗi tùy ý có thể được tạo ra. Cơ chế này tương tự như mở rộng tên tệp, nhưng tên tệp được tạo không cần tồn tại. Các mẫu được mở rộng có dạng một phần tùy chọn mở đầu, theo sau là các chuỗi được phân tách bằng dấu phẩy hoặc biểu thức seqeunce giữa một cặp dấu ngoặc, theo sau là một phần tùy chọn kết thúc. Tùy chọn mở đầu là tiền tố cho mỗi chuỗi chứa trong dấu ngoặc và sau đó phần kết thúc được thêm vào từng chuỗi kết quả, mở rộng từ trái sang phải.

Chúng ta chạy lệnh sau để hiểu hơn về vấn đề này:

[root@localhost ~]# echo test{1,2,3,4,5}.txt
test1.txt test2.txt test3.txt test4.txt test5.txt

Thực hiện một số ví dụ sau tạo đối số cho các lệnh và tiết kiệm thời gian gõ:

[root@localhost ~]# echo test.txt{,.doc,.xlsx,.pdf}
test.txt test.txt.doc test.txt.xlsx test.txt.pdf
[root@localhost ~]# echo test_{a..e}.txt
test_a.txt test_b.txt test_c.txt test_d.txt test_e.txt
[root@localhost ~]# echo mkdir /tmp/{user,bin,dev,lib64}
mkdir /tmp/user /tmp/bin /tmp/dev /tmp/lib64

Bạn có thể sử dụng mở rộng dấu ngoặc để sao chép tệp,đổi tên hoặc tạo thư mục.

#Sao chép tệp
[root@localhost ~]# cat test.txt
This is test
[root@localhost ~]# cp test.txt{,.bak}
[root@localhost ~]# cat test.txt.bak
This is test
#Đổi tên tệp
[root@localhost ~]# mv test.txt{,.doc}
[root@localhost ~]# cat test.txt.doc
This is test
#Tạo thư mục
[root@localhost ~]# mkdir /opt/{usr,lib64,dev}
[root@localhost ~]# cd /opt/
[root@localhost opt]# ls -l
total 0
drwxr-xr-x. 2 root root 6 May 26 03:06 dev
drwxr-xr-x. 2 root root 6 May 26 03:06 lib64
drwxr-xr-x. 2 root root 6 May 26 03:06 usr

3. Ví dụ các trường hợp của shell expansion

3.1. Ví dụ về mở rộng

Lệnh echo là một shell dựng sẵn, nó rất đơn giản. Thực hiện như bên dưới:

[root@localhost ~]# echo "Hello world"
Hello world

Lệnh echo được sử dụng với một văn bản đơn giản.

Tiếp theo, chúng ta gõ lệnh echo với ký tự *.

[root@localhost ~]# echo *
anaconda-ks.cfg test.txt

Bạn có thể thấy kết quả trong hình trên, tại sao lệnh echo không in ký tự * trên màn hình. Thay vào đó, nó in một danh sách các tập tin trong thư mục. Bởi vì shell đã mở rộng ký tự * này sang một thứ khác (ở đây, nó đã mở rộng ký tự * thành tên của các tệp và thư mục con có trong thư mục hiện tại) trước khi thực hiện lệnh echo.

3.2. Mở rộng tên đường dẫn

Các ký tự đại diện hoạt động được gọi là mở rộng tên đường dẫn. Chúng ta thực hiện các mở rộng như sau:

[root@localhost ~]# ls
anaconda-ks.cfg  Desktop  Documents  Music  Public  test.txt  Videos
[root@localhost ~]# echo D*
Desktop Documents
[root@localhost ~]# echo *c
Music Public
[root@localhost ~]# echo [[:upper:]]*
Desktop Documents Music Public Videos
[root@localhost ~]# echo /etc/*/network
/etc/init.d/network /etc/sysconfig/network

3.3. Mở rộng Tilde

Ký tự dấu ngã (~) khi được sử dụng ở đầu từ, nó sẽ mở rộng thành tên của thư mục chính của người dùng của người dùng hiện tại.

[root@localhost ~]# echo ~
/root

Nếu có một tài khoản người dùng thực hiện như sau:

[root@localhost ~]# echo ~dang
/home/dang

3.4. Mở rộng số học

Shell cho phép số học được thực hiện bằng cách mở rộng.

Mở rộng số học sử dụng cú pháp như sau:

$((Biểu thức))

Trong đó biểu thức là một biểu thức số học bao gồm các giá trị và toán tử số học. Mở rộng số học chỉ hỗ trợ các số nguyên không có số thập phân, có thể thực hiện khá nhiều thao tác khác nhau. Các biểu thức số học có thể được lồng nhau.

Ví dụ 1: Để nhân 7 mũ 2 với 4 chúng ta thực hiện như sau:

[root@localhost ~]# echo $(($((7**2)) * 4))
196

Ví dụ 2: Sử dụng các toán tử chia lấy phần nguyên và phân dư.

##Phép chia lấy phần nguyên
[root@localhost ~]# echo $((9/2))
4
##Phép chia lấy phần dư
[root@localhost ~]# echo $((9%2))
1

3.5. Mở rộng Brace

Với phương pháp mở rộng Brace chúng ta có thể tạo nhiều chuỗi văn bản từ một mẫu có chứa dấu ngoặc nhọn. Như bên dưới:

[root@localhost ~]# echo Gia-tri-{1,2,3,4,5}
Gia-tri-1 Gia-tri-2 Gia-tri-3 Gia-tri-4 Gia-tri-5

Bên trong dấu ngoặc có thể chứa danh sách các chuỗi được phân tách bằng dấu phẩy hoặc một phạm vi số nguyên hoặc ký tự đơn. Như ví dụ bên dưới:

[root@localhost ~]# echo So-thu-tu-{1..5}
So-thu-tu-1 So-thu-tu-2 So-thu-tu-3 So-thu-tu-4 So-thu-tu-5
[root@localhost ~]# 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

Mở rộng Brace có thể được lồng nhau như bên dưới:

[root@localhost ~]# echo a{B{1,2}}c
a{B1}c a{B2}c

Ứng dụng mở rộng Brace phổ biến nhất là tạo danh sách các tệp hoặc thư mục.

Ví dụ 1: Tạo ra một loạt các thư mục có tên định dạng Tháng Năm. Sử dụng mở rộng Brace bạn có thể thực hiện như bên dưới:

[root@localhost ~]# cd Documents/
[root@localhost Documents]# ls
[root@localhost Documents]# mkdir 0{1..9}-{2017..2019} {10..12}-{2017..2019}
[root@localhost Documents]# ls
01-2017  02-2017  03-2017  04-2017  05-2017  06-2017  07-2017  08-2017  09-2017  10-2017  11-2017  12-2017
01-2018  02-2018  03-2018  04-2018  05-2018  06-2018  07-2018  08-2018  09-2018  10-2018  11-2018  12-2018
01-2019  02-2019  03-2019  04-2019  05-2019  06-2019  07-2019  08-2019  09-2019  10-2019  11-2019  12-2019

Ví dụ 2: Thực hiện copy dữ liệu từ file test.txt vào file test.txt.bak như sau:

[root@localhost ~]# cat test.txt
This is test
[root@localhost ~]# cp test.txt{,.bak}
[root@localhost ~]# cat test.txt.bak
This is test

Để có thể copy dữ liệu vào nhiều tệp cùng một lúc từ file test.txt. Nếu cung cấp cho lệnh cp có nhiều hơn hai tham số thì tham số cuối cùng phải là một thư mục mà tất cả các tham số khác để được sao chép dữ liệu. Có thể thực hiện như sau:

[root@localhost ~]# cat test.txt
This is test
[root@localhost ~]# for f in test{10..13}.txt ; do cp test.txt $f ; done
[root@localhost ~]# cat test10.txt
This is test
[root@localhost ~]# cat test11.txt
This is test
[root@localhost ~]# cat test12.txt
This is test
[root@localhost ~]# cat test13.txt
This is test

3.6. Mở rộng tham số

Mở rộng tham số là một tính năng hữu ích trong các tập lệnh shell.

Ví dụ: Biến có tên là USER chứa tên người dùng của bạn. Gọi để mở rộng tham số và xem nội dung của USER thực hiện như sau:

[root@localhost ~]# echo $USER
root

Để xem danh sách các biến có sẵn chúng ta chạy lệnh sau: printenv | less

Với mở rộng tham số, nếu chúng ta viết sai tên của một biến, việc mở rộng sẽ vẫn diễn ra, nhưng sẽ dẫn đến một chuỗi trống như bên dưới:

[root@localhost ~]#  printenv | less
[root@localhost ~]# echo $SUER

[root@localhost ~]#

3.7. Thay thế lệnh

Thay thế lệnh cho phép chúng ta sử dụng đầu ra của lệnh dưới dạng mở rộng như sau:

[root@localhost ~]# ls
anaconda-ks.cfg  Desktop  Documents  Music  Public  test.txt  Videos
[root@localhost ~]# echo $(ls)
anaconda-ks.cfg Desktop Documents Music Public test.txt Videos

Thay thế lệnh thường được sử dụng như sau:

[root@localhost ~]# ls -l $(which passwd)
-rwsr-xr-x. 1 root root 27832 Jun 10  2014 /usr/bin/passwd

Qua kết quả của ví dụ trên chúng ta đã chuyển các kết quả của lệnh which làm đối số cho lệnh ls, do đó chúng ta có được tên đường dẫn đầy đủ của nó.

Thay thế lệnh không những sử dụng các lệnh đơn thuần mà có thể sử dụng lệnh dạng đường ống như bên dưới:

[root@localhost ~]#  file $(ls /usr/bin/* | grep bin/xz)
/usr/bin/xz:      ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=a74e88273306c2066b7bab5e7d279aa621e1f2fa, stripped
/usr/bin/xzcat:   symbolic link to `xz'
/usr/bin/xzcmp:   symbolic link to `xzdiff'
/usr/bin/xzdec:   ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=a9ad886048616b0bb9eafd403803b636f4aa1688, stripped
/usr/bin/xzdiff:  POSIX shell script, ASCII text executable
/usr/bin/xzegrep: symbolic link to `xzgrep'
/usr/bin/xzfgrep: symbolic link to `xzgrep'
/usr/bin/xzgrep:  POSIX shell script, ASCII text executable
/usr/bin/xzless:  POSIX shell script, ASCII text executable
/usr/bin/xzmore:  POSIX shell script, ASCII text executable

3.8. Trích dẫn

Chúng ta cùng thực hiện hai ví dụ sau:

Chạy lệnh sau:

[root@localhost ~]# echo Day la vi du              trich dan
Day la vi du trich dan

Ví dụ trên việc tách từ bằng shell đã loại bỏ khoảng trắng thừa khỏi danh sách các đối số của lệnh echo.

Thực hiện lệnh sau:

[root@localhost ~]# echo Tong so tien la $200.00
Tong so tien la 00.00

Ví dụ này thì mở rộng tham số đã thay thế một chuỗi rỗng cho giá trị của $2 USD vì nó là một biến không xác định.

Qua 2 ví trên thì chúng ta thấy shell đã cung cấp một cơ chế được gọi là trích dẫn để ngăn chặn có chọn lọc các mở rộng không mong muốn.

3.9. Dấu ngoặc kép

Nếu bạn đặt văn bản bên trong dấu ngoặc kép, tất cả các ký tự đặc biệt được sử dụng bởi shell sẽ mất ý nghĩa đặc biệt của chúng và được coi là các ký tự bình thường nhưng có một số trường hợp ngoại lệ như là “$”,“\” , và “`”.

Sử dụng dấu ngoặc kép đối với những tệp có tên có khoảng trắng (space). Giả sử bạn có một tệp tên test netowrk.txt. Nếu bạn không sử dụng dấu ngoặc kéo thì nó sẽ thành hai đối số riêng biệt thay vì đối số:

[root@localhost opt]# ls -l test netowrk.txt
ls: cannot access test: No such file or directory
ls: cannot access netowrk.txt: No such file or directory
[root@localhost opt]# ls -l "test netowrk.txt"
-rw-r--r--. 1 root root 0 May 26 03:34 test netowrk.txt

Chúng ta xem ảnh hưởng của dấu ngoặc kép đối với việc thay thế lệnh.

Trong ví dụ mục 3.8 chúng ta thấy các khoảng trắng thừa trong văn bản của chúng ta bị xoá:

[root@localhost ~]# echo Day la vi du              trich dan
Day la vi du trich dan

Theo mặc định sự hiện diện của khoảng trắng, tab và dòng mới (ký tự dòng) chúng được xem là dấu phân cách giữa các từ. Vì vậy chúng tách các từ thành các đối số khác nhau. Nếu chúng tôi thêm dấu ngoặc kép vào dòng lệnh trên thì kết quả sẽ như sau:

[root@localhost ~]# echo "Day la vi du              trich dan"
Day la vi du              trich dan

Qua kết quả của ví dụ trên cho chúng ta thấy khi thêm dấu ngoặc kép vào nó đã không còn là dấu phân cách giữa các từ, thay vào đó chúng trở thành một phần của đối số.

Việc các dòng mới được coi là các dấu phân cách bởi cơ chế phân tách từ gây ra hiện tượng như sau:

[root@localhost ~]# echo $(cal)
May 2019 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
[root@localhost ~]# echo "$(cal)"
      May 2019
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Trong trường hợp đầu tiên chúng được xem là dấu phân cách giữa các từ đến một dòng lệnh chứa ba mươi tám đối số. Trong trường hợp thứ hai, một dòng lệnh với một đối số bao gồm các khoảng trắng và dòng mới.

3.10. Dấu nháy đơn

Nếu bạn cần loại bỏ tất cả các phần mở rộng thì hãy sử dụng dấu ngoặc đơn. Dưới đây là so sánh các trích dẫn không trích dẫn, trích dẫn kép và trích dẫn đơn:

[root@localhost ~]# echo test ~/*.txt {a..e} $(echo blogd.net) $((4+5)) $USER
test /root/test.txt a b c d e blogd.net 9 root
[root@localhost ~]# echo "test ~/*.txt {a..e} $(echo blogd.net) $((4+5)) $USER"
test ~/*.txt {a..e} blogd.net 9 root
[root@localhost ~]# echo 'test ~/*.txt {a..e} $(echo blogd.net) $((4+5)) $USER'
test ~/*.txt {a..e} $(echo blogd.net) $((4+5)) $USER

Như kết quả 3 loại trích dẫn không trích dẫn, trích dẫn kép và trích dẫn đơn thì càng có nhiều sự mở rộng bị triệt tiêu.

4. Lời kết

Qua bài trên, giúp cho chúng ta biết cách sử dụng tính năng mở rộng (Expansion) của Bash Shell trên hệ điều hành Linux. Để chúng ta có thể ứng dụng tính nằng mở rộng này vào thực tế dể dàng hơn.