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
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.