Lý do phổ biến khiến mọi người muốn học Unix shell là để khai phá sức mạnh xử lý hàng loạt. Nếu bạn muốn thực hiện một tập hành động trên nhiều file, một cách để làm điều đó là xây dựng một lệnh lặp qua các file đó. Theo thuật ngữ lập trình, điều này gọi là kiểm soát thực thi, và một trong những ví dụ phổ biến nhất là vòng lặp for.
Vòng lặp for là công thức chi tiết hành động bạn muốn máy tính thực hiện cho mỗi đối tượng dữ liệu (như file) bạn chỉ định.
Vòng lặp for kinh điển
Một vòng lặp dễ thử là phân tích tập hợp file. Có lẽ vòng lặp này không hữu ích riêng lẻ, nhưng là cách an toàn để tự chứng minh bạn có khả năng xử lý từng file trong thư mục riêng lẻ. Trước hết, tạo môi trường test đơn giản bằng cách tạo thư mục và đặt bản sao một số file vào đó. Bất kỳ file nào cũng được ban đầu, nhưng ví dụ sau cần file đồ họa (JPEG, PNG, hoặc tương tự). Bạn có thể tạo thư mục và copy file vào bằng trình quản lý file hoặc terminal:
$ mkdir example
$ cp ~/Pictures/vacation/*.{png,jpg} example
Chuyển thư mục đến thư mục mới, sau đó liệt kê file để xác nhận môi trường test như mong đợi:
$ cd example
$ ls -1
cat.jpg
design_maori.png
otago.jpg
waterfall.png
Cú pháp để lặp qua từng file riêng lẻ trong vòng lặp là: tạo biến (f cho file, ví dụ). Sau đó xác định tập dữ liệu bạn muốn biến lặp qua. Trong trường hợp này, lặp qua tất cả file trong thư mục hiện tại dùng ký tự đại diện * (ký tự * khớp mọi thứ). Sau đó kết thúc mệnh đề giới thiệu bằng dấu chấm phẩy (;).
$ for f in * ;
Tùy sở thích, bạn có thể chọn nhấn Return ở đây. Shell sẽ không thử thực thi vòng lặp cho đến khi nó hoàn chỉnh về mặt cú pháp.
Tiếp theo, xác định điều bạn muốn xảy ra mỗi lần lặp. Để đơn giản, dùng lệnh file để lấy chút dữ liệu về mỗi file, được biểu diễn bằng biến f (nhưng thêm dấu $ ở đầu để báo cho shell thay thế giá trị biến bằng bất cứ gì biến hiện chứa):
do file $f ;
Kết thúc mệnh đề bằng dấu chấm phẩy nữa và đóng vòng lặp:
done
Nhấn Return để bắt đầu shell lặp qua mọi thứ trong thư mục hiện tại. Vòng lặp for gán từng file, lần lượt, cho biến f và chạy lệnh của bạn:
$ for f in * ; do
> file $f ;
> done
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
Bạn cũng có thể viết gọn:
$ for f in *; do file $f; done
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
Cả định dạng nhiều dòng và một dòng đều giống nhau đối với shell và cho kết quả y hệt.
Ví dụ thực tế
Đây là ví dụ thực tế về việc vòng lặp có thể hữu ích như thế nào cho tin học hàng ngày. Giả sử bạn có bộ sưu tập ảnh du lịch muốn gửi cho bạn bè. File ảnh của bạn khổng lồ, quá lớn để email và bất tiện upload lên dịch vụ chia sẻ ảnh. Bạn muốn tạo phiên bản web nhỏ hơn của ảnh, nhưng có 100 tấm và không muốn mất thời gian thu nhỏ từng tấm một.
Trước hết, cài lệnh ImageMagick dùng trình quản lý gói trên Linux, BSD, hoặc Mac. Ví dụ, trên Fedora và RHEL:
$ sudo dnf install ImageMagick
Trên Ubuntu hoặc Debian:
$ sudo apt install ImageMagick
Trên BSD, dùng ports hoặc pkgsrc. Trên Mac, dùng Homebrew hoặc MacPorts.
Sau khi cài ImageMagick, bạn có bộ lệnh mới thao tác ảnh.
Tạo thư mục đích cho file bạn sắp tạo:
$ mkdir tmp
Để thu nhỏ mỗi ảnh xuống 33% kích thước gốc, thử vòng lặp này:
$ for f in * ; do convert $f -scale 33% tmp/$f ; done
Sau đó xem thư mục tmp để thấy ảnh đã thu nhỏ.
Bạn có thể dùng bất kỳ số lệnh nào trong vòng lặp, nên nếu cần thực hiện hành động phức tạp trên một loạt file, bạn có thể đặt toàn bộ quy trình làm việc giữa do và done của vòng lặp for. Ví dụ, giả sử bạn muốn copy mỗi ảnh đã xử lý thẳng đến thư mục ảnh chia sẻ trên web host và xóa file ảnh khỏi hệ thống cục bộ:
$ for f in * ; do
convert $f -scale 33% tmp/$f
scp -i seth_web tmp/$f seth@example.com:~/public_html
trash tmp/$f ;
done
Với mỗi file được xử lý bởi vòng lặp for, máy tính tự động chạy ba lệnh. Nghĩa là nếu bạn xử lý chỉ 10 ảnh theo cách này, bạn tự cứu mình 30 lệnh và có lẽ ít nhất cũng bấy nhiêu phút.
Giới hạn vòng lặp
Vòng lặp không phải luôn nhìn mọi file. Bạn có thể chỉ muốn xử lý file JPEG trong thư mục example:
$ for f in *.jpg ; do convert $f -scale 33% tmp/$f ; done
$ ls -m tmp
cat.jpg, otago.jpg
Hoặc, thay vì xử lý file, bạn có thể cần lặp lại hành động một số lần cụ thể. Biến của vòng lặp for được định nghĩa bởi dữ liệu bạn cung cấp, nên bạn có thể tạo vòng lặp lặp qua số thay vì file:
$ for n in {0..4}; do echo $n ; done
0
1
2
3
4
Vòng lặp thêm
Bây giờ bạn đã biết đủ để tạo vòng lặp riêng. Cho đến khi bạn thoải mái với vòng lặp, hãy dùng chúng trên bản sao của file bạn muốn xử lý và, càng thường càng tốt, dùng lệnh có bảo vệ tích hợp để ngăn chặn việc phá hoại dữ liệu và lỗi không thể khắc phục, như vô tình đổi tên toàn bộ thư mục file thành cùng một tên, mỗi lần ghi đè lên nhau.
Để biết chủ đề vòng lặp for nâng cao, hãy đọc tiếp.
Không phải shell nào cũng là Bash
Từ khóa for tích hợp trong shell Bash. Nhiều shell tương tự dùng cùng từ khóa và cú pháp, nhưng một số shell, như tcsh, dùng từ khóa khác, như foreach.
Trong tcsh, cú pháp tương tự về tinh thần nhưng chặt chẽ hơn Bash. Trong mẫu mã sau, đừng gõ chuỗi foreach? ở dòng 2 và 3. Đó là prompt thứ cấp báo bạn vẫn đang xây dựng vòng lặp.
$ foreach f (*)
foreach? file $f
foreach? end
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
Trong tcsh, cả foreach và end phải xuất hiện riêng trên dòng riêng, nên bạn không thể tạo vòng lặp for trên một dòng như Bash và shell tương tự.
Vòng lặp for với lệnh find
Về lý thuyết, bạn có thể tìm thấy shell không cung cấp hàm vòng lặp for, hoặc bạn chỉ thích dùng lệnh khác với tính năng thêm.
Lệnh find là cách khác triển khai chức năng vòng lặp for, vì nó cung cấp vài cách xác định phạm vi file nào đưa vào vòng lặp cũng như tùy chọn xử lý song song.
Lệnh find nhằm giúp bạn tìm file trên ổ cứng. Cú pháp đơn giản: bạn cung cấp đường dẫn vị trí muốn tìm, và find tìm tất cả file và thư mục:
$ find .
.
./cat.jpg
./design_maori.png
./otago.jpg
./waterfall.png
Bạn có thể lọc kết quả tìm kiếm bằng cách thêm phần tên:
$ find . -name "*jpg"
./cat.jpg
./otago.jpg
Điều tuyệt vời về find là mỗi file nó tìm được có thể đưa vào vòng lặp dùng cờ -exec. Ví dụ, để chỉ thu nhỏ ảnh PNG trong thư mục example:
$ find . -name "*png" -exec convert {} -scale 33% tmp/{} \;
$ ls -m tmp
design_maori.png, waterfall.png
Trong mệnh đề -exec, ký tự ngoặc nhọn {} thay thế cho mục find đang xử lý (nói cách khác, bất kỳ file kết thúc bằng PNG đã được định vị, lần lượt một). Mệnh đề -exec phải kết thúc bằng dấu chấm phẩy, nhưng Bash thường tự dùng dấu chấm phẩy cho mình. Bạn "escape" dấu chấm phẩy bằng gạch chéo ngược (\;) để find biết xử lý dấu chấm phẩy đó như ký tự kết thúc của nó.
Lệnh find rất giỏi việc của nó, và đôi khi quá giỏi. Ví dụ, nếu bạn dùng lại nó tìm file PNG cho quy trình ảnh khác, bạn sẽ gặp vài lỗi:
$ find . -name "*png" -exec convert {} -flip -flop tmp/{} \;
convert: unable to open image `tmp/./tmp/design_maori.png':
No such file or directory @ error/blob.c/OpenBlob/2643.
...
Có vẻ find đã định vị tất cả file PNG—không chỉ những file trong thư mục hiện tại (.) mà còn những file bạn xử lý trước và đặt trong thư mục con tmp. Trong một số trường hợp, bạn có thể muốn find tìm kiếm thư mục hiện tại cộng với tất cả thư mục khác bên trong nó (và tất cả thư mục trong những thư mục đó). Nó có thể là công cụ xử lý đệ quy mạnh mẽ, đặc biệt trong cấu trúc file phức tạp (như thư mục nghệ sĩ nhạc chứa thư mục album đầy file nhạc), nhưng bạn có thể giới hạn bằng tùy chọn -maxdepth.
Để chỉ tìm file PNG trong thư mục hiện tại (loại trừ thư mục con):
$ find . -maxdepth 1 -name "*png"
Để tìm và xử lý file trong thư mục hiện tại cộng thêm một cấp thư mục con, tăng độ sâu tối đa 1:
$ find . -maxdepth 2 -name "*png"
Mặc định của nó là đi sâu vào tất cả thư mục con.
Vòng lặp cho vui và lợi ích
Càng dùng vòng lặp nhiều, bạn càng tiết kiệm thời gian và công sức, và tác vụ lớn hơn bạn có thể giải quyết. Bạn chỉ là một người dùng, nhưng với vòng lặp suy nghĩ kỹ, bạn có thể làm máy tính làm việc vất vả.
Bạn có thể và nên đối xử với vòng lặp như bất kỳ lệnh nào khác, giữ nó gần tay khi cần lặp lại hành động đơn lẻ hoặc hai trên vài file. Tuy nhiên, nó cũng là cánh cửa chính thống vào lập trình nghiêm túc, nên nếu bạn phải hoàn thành tác vụ phức tạp trên bất kỳ số lượng file nào, hãy dành chút thời gian lên kế hoạch quy trình làm việc. Nếu bạn đạt được mục tiêu trên một file, thì gói quy trình lặp lại đó vào vòng lặp for tương đối đơn giản, và "lập trình" duy nhất cần thiết là hiểu cách biến hoạt động và đủ tổ chức để tách file chưa xử lý và đã xử lý. Với chút thực hành, bạn có thể từ người dùng Linux thành người dùng Linux biết viết vòng lặp, vì vậy hãy ra ngoài và làm máy tính làm việc cho bạn!