2.4. Essential Commands: grep, regular expression, archive và I/O redirection
Mục lục
- 1. Tìm chuỗi text với grep
- 2. Regular expression cơ bản
- 3. Extended regular expression
- 4. Range set và subexpression
- 5. Đóng gói với tar
- 6. Nén và giải nén
- 7. Backup với rsync và dd
- 8. Redirect input và output
- Nguồn tham khảo
1. Tìm chuỗi text với grep
Có những file chứa hàng nghìn, hàng chục nghìn dòng text, và cần một cách lọc ra đúng thông tin cần tìm. Lệnh làm việc đó là grep.
Cú pháp chung dùng xuyên suốt phần này: grep, các option, search pattern, rồi file.
grep -i 'password' /etc/ssh/sshd_config
Nên tập thói quen bọc search pattern trong dấu nháy đơn để Bash không diễn giải các ký tự đặc biệt. Trong những phép tìm phức tạp, pattern có thể chứa ký tự như dấu sao; quên bọc nháy đơn thì lệnh chạy ra kết quả không như mong đợi. Phần option bỏ qua được, nhưng search pattern và file cần tìm thì bắt buộc phải có.
Phân biệt hoa thường
Tìm mọi dòng nhắc tới chữ password trong configuration file của SSH daemon:
grep 'password' /etc/ssh/sshd_config
Phép tìm phân biệt hoa thường, nên lệnh trên chỉ ra những chỗ có chữ password viết toàn chữ thường. Tìm những dòng chứa Password với chữ P hoa thì chạy:
grep 'Password' /etc/ssh/sshd_config
Kết quả khác đi, vì những dòng khớp đã khác.
Nhiều tình huống, phép tìm phân biệt hoa thường lại phản tác dụng vì nó cho kết quả không đầy đủ. Khi cần mọi dòng nhắc tới chuyện cấu hình password, không quan tâm chữ P hoa hay thường, dùng option ignore case:
grep -i 'password' /etc/ssh/sshd_config
Nó khớp mọi dòng chứa từ đó bất kể chữ hoa chữ thường.
Tìm đệ quy trong cả một directory
Thay vì tìm trong một file cụ thể, grep tìm được trong mọi file nằm dưới một directory và các subdirectory của nó. Thêm option -r (recursive) và thay path tới file bằng path tới directory:
grep -r 'password' /etc
Mặc định grep dùng màu để highlight những phần quan trọng cho output dễ quét mắt. Ở chế độ đệ quy, tên file khớp cũng được highlight để nhìn ra ngay chuỗi được tìm thấy ở đâu.
Trong output sẽ có những dòng báo Permission denied. Lý do: user thường không đọc được một số file trong đó. Gộp option đệ quy với option ignore case cũng được:
grep -ri 'password' /etc
Muốn tìm trong những system file mà chỉ account quản trị root đọc được thì thêm sudo phía trước. Nhưng ngay lập tức có một khác biệt: output không còn được tô màu nữa, khó đọc và khó nhìn ra chỗ cần tìm. Bắt grep tô màu trở lại bằng option --color:
sudo grep -r --color 'password' /etc
Đảo ngược kết quả
Tìm những dòng không chứa chuỗi password bằng option -v:
grep -v 'password' /etc/ssh/sshd_config
Chỉ khớp đúng từ
Xét lệnh sau:
grep 'password' /etc/ssh/sshd_config
Lệnh này khớp cả password số ít và passwords số nhiều, vì dạng số nhiều chứa trọn từ bên trong, chỉ thêm chữ s ở cuối. Khi chỉ quan tâm đúng từ password, dùng option -w (word):
grep -w 'password' /etc/ssh/sshd_config
Kết quả rút lại còn đúng hai dòng. Dòng PasswordAuthentication yes không xuất hiện, vì với grep thì PasswordAuthentication là một từ khác, giống như với con người, từ book không mang nghĩa của từ booking.
Khi dùng -w, grep chỉ khớp đúng từ đó, không cho phép thêm chữ cái nào phía trước hay phía sau. Nhưng từ đó vẫn được phép có dấu trừ đứng trước, hay dấu chấm kết thúc câu đứng sau: dấu câu nào cũng được, chỉ chữ cái là không.
Chỉ in phần khớp
Mặc định grep in ra cả dòng khớp. Cách đó hữu ích vì nó cho thấy toàn bộ ngữ cảnh, biết được chuỗi ký tự hay từ khớp nằm ở đâu. Nhưng có lúc chỉ cần đúng kết quả, không quan tâm phần còn lại của dòng. Option -o (only matching) làm việc đó:
grep -o 'password' /etc/ssh/sshd_config
Nhìn bằng mắt thì output kiểu này không hữu dụng mấy với con người, nhưng nó có giá trị trong những task tự động trích xuất và xử lý dữ liệu. Dùng kèm search pattern phức tạp hơn thì nó càng có ích, vì danh sách kết quả không còn lặp lại cùng một từ mà cho ra những kết quả khác nhau. Ví dụ với một search pattern cho biết application đang bật những setting nào, output sẽ chỉ gồm tên các setting đang bật, không kèm phần còn lại của dòng vì phần đó không có giá trị trong trường hợp này.
2. Regular expression cơ bản
Các search pattern ở mục trên đều đơn giản, chỉ tìm một mẩu text cụ thể như PASS. Những điều kiện tìm phức tạp hơn cần công cụ khác.
Giả sử có code của một application nằm rải rác trong hàng trăm file, và cần lấy ra mọi địa chỉ IP được dùng trong app đó. Một IP có dạng 203.102.3.5, nhưng không thể viết một search pattern tìm "các số có dấu chấm giữa chúng", vì như vậy nó khớp cả những số như 5.23, mà đó không phải IP.
Trong toán có thể viết: x là số nguyên, x lớn hơn 3, x nhỏ hơn 8, và như vậy x là 4, 5, 6 hoặc 7. Regular expression (viết tắt là regex) hoạt động theo kiểu tương tự: chỉ ra một số điều kiện, buộc chúng lại với nhau, rồi search pattern chỉ khớp những gì vừa khít các điều kiện đó.
Mọi regular expression được xây từ các toán tử: dấu caret, dấu đô la, dấu chấm, dấu sao, dấu cộng, dấu ngoặc nhọn, dấu hỏi, dấu gạch dọc, dấu ngoặc đơn, dấu ngoặc vuông, và dấu ngoặc vuông kèm caret.
Dòng bắt đầu bằng
Trong Linux, configuration file có những dòng bắt đầu bằng dấu thăng. Đó là commented line: chúng không có hiệu lực, chỉ là lời ghi chú. Program đọc setting trong file sẽ bỏ qua mọi dòng bắt đầu bằng dấu thăng. Nhưng comment hữu ích với con người, vì chúng cho thấy ví dụ về các config setting trong file đó, mô tả chúng làm gì, mà không can thiệp vào program đọc file.
Vậy tìm riêng những dòng comment bằng một regex tìm mọi dòng bắt đầu bằng dấu thăng:
grep '^#' /etc/login.defs
Lệnh này chưa hữu dụng lắm, nó chỉ in ra đúng những dòng comment. Nhưng ghép với option đảo kết quả của grep thì khác: bảo grep hiện những dòng không bắt đầu bằng dấu thăng.
grep -v '^#' /etc/login.defs
Kết quả là đúng những gì cần: các setting đang có hiệu lực. Với một file dài hàng trăm dòng comment, cách này rất đáng giá.
Ví dụ khác, tìm những dòng bắt đầu đúng bằng bốn chữ PASS: đặt dấu caret trước search pattern.
grep '^PASS' /etc/login.defs
Dòng kết thúc bằng
Giả sử cần đổi một setting đang có giá trị là bảy ngày. Tìm chữ số 7 thì ra nhiều thứ không cần. Nhưng đã biết file này dùng cú pháp "tên biến, một dấu cách, giá trị biến": giá trị nằm cuối, nên biến nào có giá trị 7 thì số đó là ký tự cuối cùng của dòng.
grep -w '7$' /etc/login.defs
Ở đây có thêm option -w. Không có nó, một dòng khác cũng khớp: dòng có nhiều chữ số 7 ở cuối. Với -w, yêu cầu trở thành chỉ khớp những gì chứa đúng một chữ số 7 ở cuối.
Tìm mọi dòng kết thúc bằng chữ mail:
grep 'mail$' /etc/login.defs
Chú ý vị trí của hai toán tử này khác nhau. Đặt sai chỗ thì không ra kết quả nào, dễ dẫn tới bối rối kiểu tại sao regex không chạy. Cách nhớ vị trí: toán tử "dòng bắt đầu bằng", tức dấu caret, đặt ở đầu search pattern; toán tử "dòng kết thúc bằng", tức dấu đô la, đặt ở cuối search pattern.
Một ký tự bất kỳ
Ở bất cứ chỗ nào đặt một dấu chấm trong biểu thức, nó khớp một ký tự bất kỳ tại vị trí đó. Ví dụ c.t khớp cat, cut, CRT, và cả c1t hay c#t. Nhưng nó không khớp ct: giữa c và t phải có đúng một ký tự.
grep 'c.t' /etc/login.defs
Với c..t, nó chỉ khớp những gì có hai ký tự ở giữa. Chuỗi c.t khớp cả từ execute, vì dãy cut nằm vừa bên trong từ đó. Muốn chỉ khớp trọn từ chứ không khớp phần của từ lớn hơn, lại dùng option -w:
grep -w 'c.t' /etc/login.defs
Escape để tìm chính ký tự đặc biệt
Dấu chấm có nghĩa đặc biệt trong regex, vậy tìm chính một dấu chấm trong text thì làm sao? Viết thẳng dấu chấm thì không được, vì regex đó khớp lần lượt từng ký tự.
Cách xử lý là tìm một dấu chấm theo nghĩa chữ, bằng cách escape nó. Escape là cách nói với grep rằng đừng coi dấu chấm này là toán tử khớp-một-ký-tự, hãy coi nó là một dấu chấm. Escape một số ký tự đặc biệt chỉ cần thêm một dấu backslash phía trước, nên thay vì viết dấu chấm thì viết backslash rồi dấu chấm:
grep '\.' /etc/login.defs
Dấu sao
Một biểu thức như let* khớp le, let, lett, và tiếp tục như vậy, bất kể có bao nhiêu chữ t ở cuối. Cách nói khác: dấu sao cho phép element đứng ngay trước nó vắng mặt hoàn toàn, xuất hiện một lần, hoặc xuất hiện hai lần trở lên.
grep 'let*' /etc/login.defs
Dấu sao ghép được với các toán tử khác. Ví dụ tìm những dãy bắt đầu bằng một dấu gạch chéo, có zero hoặc nhiều ký tự ở giữa, rồi kết thúc bằng một dấu gạch chéo nữa:
grep '/.*/' /etc/login.defs
Vì dấu chấm khớp một ký tự bất kỳ, và dấu sao cho phép element trước nó tồn tại zero, một, hai hay nhiều lần, biểu thức này cho phép mọi dãy ký tự nằm giữa hai dấu gạch chéo đó.
Dấu cộng
Giả sử cần tìm mọi dãy ký tự nơi chữ số 0 xuất hiện một lần trở lên. Rất dễ dùng sai cách sau:
grep '0*' /etc/login.defs
Lệnh này khớp cả những dòng không có chữ số 0 nào, vì dấu sao cho phép ký tự trước nó tồn tại một lần trở lên nhưng cũng cho phép zero lần: nó khiến element đó trở thành tùy chọn trong phép tìm.
Vậy cần một toán tử khác, buộc element phải tồn tại ít nhất một lần, hoặc nhiều lần hơn. Dấu cộng làm việc đó. Với 0+, kết quả là những chuỗi như 0, 00, 000, và tiếp tục như vậy. Nhưng viết thẳng vào grep thì ra kết quả sai: dấu cộng chạy như một dấu cộng theo nghĩa chữ, không phải như một toán tử.
Lý do: mặc định grep dùng basic regular expression, và manual page của nó viết rằng trong basic regular expression, các metacharacter mất ý nghĩa đặc biệt, thay vào đó hãy dùng bản có backslash của chúng. Vậy để dấu cộng hoạt động như toán tử, phải thêm backslash phía trước:
grep '0\+' /etc/login.defs
Chuyện này rối lên rất nhanh. Vừa dùng backslash để biến toán tử dấu chấm thành một dấu chấm thường, giờ lại dùng backslash để biến một dấu cộng thường thành toán tử dấu cộng. Theo dõi cái gì cần backslash và cái gì không rất mệt. Có một đường dễ hơn: dùng extended regex.
3. Extended regular expression
Escape trong grep hoạt động theo hai hướng ngược nhau. Có lúc nó biến toán tử dấu chấm thành ký tự dấu chấm thường. Lúc khác nó làm điều trái lại: escape một dấu cộng biến nó từ dấu cộng thường thành toán tử dấu cộng.
Để tránh lẫn lộn, dùng extended regular expression. Với nó không cần nhớ ký tự nào phải escape để grep coi là toán tử regex; chỉ còn phải escape khi muốn một ký tự chuyển từ toán tử về ký tự thường, như dấu cộng hay dấu chấm.
Bật extended regex bằng option -E, chữ E hoa:
grep -E '0+' /etc/login.defs
Hoặc dễ hơn nữa, dùng lệnh tương đương egrep:
egrep '0+' /etc/login.defs
Gõ egrep giống hệt gõ grep -E, nên có thể tập thói quen luôn dùng egrep thay grep để khỏi mắc lỗi quên escape một toán tử regex bằng backslash.
Dấu ngoặc nhọn để chỉ định số lần lặp
Tìm mọi chuỗi chứa ít nhất ba chữ số 0 thì dùng toán tử ngoặc nhọn. Bên trong ghi hai giá trị cách nhau bằng dấu phẩy: giá trị đầu là số lần lặp tối thiểu của element đứng trước, giá trị sau là số lần lặp tối đa. Ở đây cần tối thiểu ba lần lặp, và không muốn đặt giới hạn trên, nên bỏ trống giá trị thứ hai:
egrep '0{3,}' /etc/login.defs
Tìm mọi chuỗi chứa một chữ số 1 theo sau là nhiều nhất ba chữ số 0: thêm số 1 vào search pattern, rồi tiếp chữ số 0 kèm biểu thức lặp, lần này ngược lại. Bỏ trống trường tối thiểu và ghi số tối đa ở trường thứ hai:
egrep '10{,3}' /etc/login.defs
Lệnh này cũng khớp những số 1 không có chữ số 0 nào theo sau, vì không chọn mức tối thiểu cụ thể nào.
Tìm mọi chuỗi chứa đúng ba chữ số 0 thì dùng quantifier theo cách khác: thay vì hai trường tối thiểu và tối đa, chỉ đặt một giá trị giữa hai dấu ngoặc nhọn:
egrep '0{3}' /etc/login.defs
Dấu hỏi để làm element thành tùy chọn
Dấu hỏi cho phép element đứng trước nó tồn tại đúng zero hoặc một lần, nghĩa là element đó trở thành tùy chọn: có thể có một lần, hoặc không có.
Giả sử cần tìm mọi đoạn text ghi disabled hoặc disable. Chữ d cuối là tùy chọn, nên biểu thức viết như sau:
egrep 'disabled?' /etc/login.defs
Biểu thức này cũng khớp từ disable nằm trong một từ dài hơn, vì chữ d không rơi đúng cuối từ. Giống các trường hợp trước, thêm option -w để chỉ còn disable hoặc disabled khớp:
egrep -w 'disabled?' /etc/login.defs
Dấu gạch dọc để chọn một trong hai
Muốn khớp enabled hoặc disabled thì dùng toán tử OR: nó khớp thứ tìm thấy ở bên trái hoặc ở bên phải nó.
egrep 'enabled|disabled' /etc/login.defs
Ghép với mẹo làm chữ d cuối thành tùy chọn ở trên để bắt luôn các biến thể enable, enabled, disable, disabled. Thêm option không phân biệt hoa thường để bắt cả những biến thể có chữ hoa ở một vài vị trí:
egrep -i 'enabled?|disabled?' /etc/login.defs
4. Range set và subexpression
Giờ ghép nhiều toán tử regex lại để tinh chỉnh phép tìm. Trước đó cần biết thêm về range và set.
Một range viết trong cặp ngoặc vuông, dạng [a-z]. Nó khớp một chữ cái thường bất kỳ từ a, b, c, d cho tới z. Còn [0-9] khớp một chữ số bất kỳ từ 0 tới 9.
Một set viết theo dạng [abz954]. Nó khớp một ký tự bất kỳ trong số a, b, z, 9, 5, 4.
Tìm mọi chuỗi chứa chữ cat hoặc cut:
egrep 'c[au]t' /etc/login.defs
Với range và set, phép tìm vừa rộng vừa cụ thể cùng lúc.
Thu hẹp dần một regex thực tế
Câu hỏi: làm sao tìm mọi config file nhắc tới các special device file trên máy, với tên kiểu /dev/sda1 hoặc tương tự?
Cách nghĩ đầu tiên là tìm mọi chuỗi chứa /dev/ rồi theo sau là ký tự bất kỳ:
egrep '/dev/.*' /etc/login.defs
Nhưng nó khớp những thứ kỳ quặc. Mẫu .* là greedy matching: sau khi bắt được phần /dev/ cần tìm, nó khớp quá nhiều thứ phía sau. Phép tìm cần rộng đủ để bắt mọi tên device trong /dev, nhưng cụ thể đủ để chỉ bắt đúng phần cần.
Dùng range: sau /dev/, khớp một số lượng ký tự thường bất kỳ từ a tới z, và "số lượng bất kỳ" thì dùng toán tử dấu sao. Ở đây dấu sao áp dụng cho set đứng ngay trước nó, tức a-z:
egrep '/dev/[a-z]*' /etc/login.defs
Output đã tốt hơn, nhưng vẫn sót. /dev/tty được khớp thay vì trọn /dev/tty0. Bắt luôn chữ số ở cuối bằng cách quy định tại vị trí đó phải có một chữ số từ 0 tới 9:
egrep '/dev/[a-z]*[0-9]' /etc/login.defs
Nhưng lại sinh vấn đề mới: chỉ những thứ có chữ số ở cuối mới khớp. Nó tìm ra /dev/sda1 nhưng bỏ qua /dev/sda. Cách sửa là làm chữ số ở cuối thành tùy chọn bằng toán tử dấu hỏi:
egrep '/dev/[a-z]*[0-9]?' /etc/login.defs
Subexpression
Trong toán, 1 + 2 * 3 cho ra 7, vì phép nhân giữa 2 và 3 được làm trước, rồi mới tới phép cộng. Muốn cộng 1 với 2 trước rồi mới nhân với 3 thì viết (1 + 2) * 3: phép cộng làm trước, sau đó phép nhân, ra 3 nhân 3 bằng 9. Regex làm một việc rất giống vậy.
Xem lại biểu thức vừa dựng. Cuộn output lên sẽ thấy vẫn chưa khớp hết những gì cần. Ở một dòng nào đó, phần p0 bị bỏ ra ngoài. Lý do: biểu thức sau khi tìm thấy /dev thì khớp một số lượng ký tự a-z bất kỳ rồi một chữ số ở cuối, và hết. Với /dev/tty0p0, sau khi chạm chữ số 0 đầu tiên thì regex coi như xong, chấp nhận kết quả khớp một phần.
Cách sửa: nói với nó rằng sau /dev, có một số chữ cái rồi một chữ số ở cuối, nhưng sau đó chính cụm vừa rồi có thể lặp lại zero, một, hai, ba hay nhiều lần nữa. Có thể có thêm những dãy chữ cái kèm chữ số. Như vậy /dev/tty0 khớp trước, rồi tty0p0 được cộng vào kết quả khớp đó nhờ phần lặp.
Toán tử cho "tồn tại zero lần hoặc nhiều lần" là dấu sao. Nhưng thêm nó vào cuối thì dấu sao chỉ áp dụng cho element đứng ngay trước, tức dấu hỏi, và như vậy không giải quyết được gì. Cần nó áp dụng cho cả cụm. Giải pháp là bọc cụm đó trong cặp ngoặc đơn, lúc đó dấu sao áp dụng cho toàn bộ subexpression chứ không chỉ element cuối:
egrep '/dev/([a-z]*[0-9]?)*' /etc/login.defs
Tương tự khi muốn tìm chữ cái thường hoặc chữ cái hoa bằng toán tử OR. Viết [a-z]|[A-Z]* là sai, vì dấu sao chỉ áp dụng cho [A-Z], trong khi cần nó áp dụng cho cả cấu trúc. Lại bọc vào ngoặc đơn thành subexpression:
egrep '/dev/(([a-z]|[A-Z])*[0-9]?)*' /etc/login.defs
Giờ /dev/ttyS0 khớp. Đi tiếp theo cùng logic này còn sửa được những trường hợp như /dev/term/a chưa khớp, vì regex dừng lại khi gặp dấu gạch chéo tiếp theo. Đây là kiểu suy luận và tinh chỉnh phải đi qua khi sửa một regular expression hoặc khi muốn nó nhắm chính xác vào thứ cần tìm.
Phủ định một set hoặc range
Set có dạng [abc123], range có dạng [a-z]. Thêm toán tử caret vào trong là phủ định chúng: nó nói với regex rằng các element trong set hay range này không được tồn tại ở vị trí đó.
Giả sử cần tìm chuỗi https nhưng không muốn ra các link tới website. Vì một link thường có dạng https:, biểu thức sau nói "tìm chuỗi https, nhưng chắc chắn sau nó không phải dấu hai chấm":
egrep 'https[^:]' /etc/login.defs
Tìm chuỗi http không theo sau bởi chữ s cũng không theo sau bởi dấu hai chấm thì phủ định cả set, tức liệt kê những gì cần phủ định:
egrep 'http[^s:]' /etc/login.defs
Tới đây set chỉ có một hoặc hai ký tự, nhưng range cũng phủ định được. Ví dụ quy định sau một dấu gạch chéo thì không được có chữ cái thường nào:
egrep '/[^a-z]' /etc/login.defs
Với bất kỳ pattern nào cần khớp cũng có nhiều lời giải regex khác nhau. Làm đúng được thì phải luyện tới lúc thấy thoải mái với regular expression. Regex cũng không chỉ dùng trong grep: nó dùng được trong rất nhiều program có xử lý search pattern, ví dụ utility sed cũng hỗ trợ regular expression. Muốn thử nhiều regular expression và xem ngay tác dụng của chúng thì những website như regexr.com khá hữu ích.
5. Đóng gói với tar
Có một website với hàng nghìn file và directory, và không muốn mất gì nên quyết định back up toàn bộ dữ liệu đó. Làm tay thì các bước thường là:
- Gói toàn bộ file và directory đó vào một file duy nhất, ví dụ
backup.tar. Hành động này gọi là archiving. - Nén
backup.tarlại, để 10 gigabyte dữ liệu được chứa trong một file nén nhỏ hơn, có thể chỉ 7 hoặc 8 gigabyte, có khi còn ít hơn. Kết quả là một file nhưbackup.tar.gz. - Copy file nén đó tới một vị trí ở xa, để có bản thứ hai của dữ liệu ở một nơi khác. Đó là backup.
Tên tar đến từ Tape Archive. Từng có thời giữ backup trên băng từ rất được ưa dùng, và tar được dùng để chuẩn bị dữ liệu gửi ra những cuốn băng đó. Dù format backup trên băng ngày nay ít dùng, nếu còn dùng, tar vẫn là công cụ phổ biến vì nó làm tốt việc của nó.
tar là một bộ đóng gói và mở gói. Nó nhận bất kỳ số lượng file và directory rời rạc rồi gói hết vào một file .tar duy nhất, còn gọi là tarball. Có mọi file trong một gói thì chuyển nó đi, upload lên website, cho người khác tải archive về đều dễ hơn.
Ba cách viết option
Giả sử có một file archive .tar trên máy. tar cho phép chỉ định command-line option theo ba cách khác nhau. Ví dụ để hiện nội dung của archive, option đúng là --list, hoặc -t, hoặc chỉ t, dạng ngắn nhất. Nên cả ba dạng sau đều dùng được:
tar --list --file archive.tar
tar -t -f archive.tar
tar tf archive.tar
Nhớ được mỗi chữ cái như t hay f làm gì rồi thì sẽ thích dạng cuối, vì nó gõ nhanh nhất. Nhưng lúc đầu rất khó nhớ chữ cái nào dùng để liệt kê (t), còn --list thì dễ nhớ hơn nhiều.
Chi tiết quan trọng nhất: khi trỏ tar tới một archive .tar, nên tập thói quen đặt option --file, hay -f, ở cuối danh sách option. Lý do là tar mong đợi path của file .tar nằm ngay sau option --file. Đặt option đó ở chỗ khác, chẳng hạn ở đầu, rồi thêm một option thứ hai ngay sau nó, thì tar sẽ nhầm, tưởng option thứ hai chính là path tới file.
Tạo và thêm vào archive
Lấy file1 rồi gói vào một file tên archive.tar:
tar --create --file archive.tar file1
--create cho tar biết cần tạo một tar archive mới, với tên được ghi sau --file.
Thêm một file nữa vào archive đã có:
tar --append --file archive.tar file2
--append là cách nói sang của "thêm vào một archive đang tồn tại".
Thêm trọn một directory cùng nội dung của nó:
tar --append --file archive.tar Pictures/
Dùng relative path như Pictures/ thì path được lưu trong archive cũng là dạng tương đối. Dùng absolute path thì nội dung lưu trong archive mang path tuyệt đối.
Luôn liệt kê trước khi extract
Trước khi extract từ một tar archive, luôn chạy lệnh liệt kê và xem kỹ các path bên trong, để biết file sắp extract ra sẽ rơi vào đâu:
tar --list --file archive.tar
# Output (minh họa):
Pictures/
Pictures/family_dog.jpg
Mặc định tar extract vào current directory, nên file sẽ nằm ở current directory cộng với các path đã thấy trong archive. Đang ở /home/aaron/work thì các file này ra tại /home/aaron/work/Pictures.
tar --extract --file archive.tar
Muốn extract sang một directory khác thì nói với tar đừng extract vào current directory mà vào directory chỉ định. Ví dụ đang ở /home/aaron, có archive.tar tại đó, nhưng muốn nội dung ra /tmp:
tar --extract --file archive.tar --directory /tmp
Khôi phục đúng ownership và permission
Một tar archive cũng lưu thông tin permission và ownership của mọi file và directory bên trong. Nhưng nếu đang đăng nhập bằng aaron mà một file trong archive thuộc jane, aaron không có permission để tạo một file thuộc sở hữu của jane. User root thì có đặc quyền đó. Vậy muốn chắc chắn mọi thông tin ownership và permission được khôi phục đúng như lúc được chụp lại thì chạy qua sudo:
sudo tar --extract --file archive.tar
sudo là lệnh tạm thời cấp quyền root, chi tiết nằm ở Part 4.2: sudo, truy cập root và xác thực tập trung.
6. Nén và giải nén
Giảm kích thước file bằng cách nén không chỉ tiết kiệm chỗ lưu trữ, nó còn giúp chuyển file giữa các hệ thống nhanh hơn nhiều.
Phần lớn hệ thống Linux có sẵn ít nhất ba utility nén: gzip, bzip2 và xz. Nén một file bằng chúng rất đơn giản:
gzip file1
bzip2 file2
xz file3
Ba lệnh trên nén file1, file2, file3, tạo bản nén của chúng, và tự động xóa file gốc sau đó. Kết quả là file1.gz, file2.bz2, file3.xz.
Giải nén thì dùng các lệnh tương ứng:
gunzip file1.gz
bunzip2 file2.bz2
unxz file3.xz
Khi file1.gz được giải nén, chuyện ngược lại xảy ra: file gốc chưa nén file1 được tạo lại, và file nén file1.gz bị xóa.
Giữ lại file gốc
Có lúc việc tự động xóa file nén hoặc file gốc như vậy không mong muốn: gzip file1 rồi nhưng vẫn muốn giữ file1 lại. Cách tra là xem các command-line option mà program hỗ trợ bằng --help:
gzip --help
Trong đó có option --keep, dạng ngắn là -k, đúng cái đang cần. Nên muốn giữ file gốc thì:
gzip --keep file1
bzip2 --keep file2
xz --keep file3
Nếu cần xem nội dung của một file nén, một số lệnh hỗ trợ option --list.
zip gói được cả directory
Ba utility trên thường chỉ dùng để nén và giải nén. Nhưng đôi khi sẽ gặp thêm những program khác, ví dụ zip. Một lợi thế của zip là nó gói và nén được trọn directory, hoặc nhiều file, vào cùng một archive; trong khi những utility như gzip chỉ tạo được dữ liệu nén chứa đúng một file bên trong.
Tạo một archive tên archive.zip và nén file1 vào trong nó:
zip archive.zip file1
Gói và nén trọn directory Pictures vào archive.zip:
zip -r archive.zip Pictures
-r nén đệ quy mọi file và subdirectory trong Pictures.
Mở gói và giải nén một file zip:
unzip archive.zip
Nhắc lại cho rõ: gzip không có option nào lấy một directory rồi nhồi vào một archive duy nhất. Nó không gói được nhiều file vào một file. Nó chỉ nén được đúng một file. zip thì làm được cả archiving lẫn compression.
Ghép tar với utility nén
Vì gzip, bzip2, xz không làm phần archiving, chúng thường được dùng kèm tar. Sau khi tạo file tar, nén nó bằng utility nào cũng được:
tar --create --file archive.tar Pictures/
gzip archive.tar
Nhưng thay vì hai bước, có thể bảo chính tar làm cả hai việc trong một bước. Tốt hơn nữa, tar có option --auto-compress để tự suy ra utility nén nào cần dùng dựa trên phần mở rộng đặt cho archive: .gz thì nén bằng gzip, .bz2 thì nén bằng bzip2, .xz thì nén bằng xz.
tar --create --auto-compress --file archive.tar.gz Pictures/
Lúc mở gói và giải nén thì không cần nói với tar dùng utility giải nén nào, nó tự suy ra được. Một lệnh như sau là đủ:
tar --extract --file archive.tar.gz
7. Backup với rsync và dd
Có rất nhiều utility và công cụ tinh vi để làm backup, nhưng phạm vi ở đây là làm backup theo cách cơ bản nhất: copy file từ hệ thống này sang hệ thống khác được chỉ định làm vị trí backup. Mục tiêu là hiểu những công cụ native của Linux làm được việc đó.
rsync
Tên rsync đến từ remote synchronization. Nó đồng bộ /some/directory trên server một với /some/other/directory trên server hai, bằng cách copy dữ liệu qua một network connection. Server ở xa phải có một SSH daemon đang chạy.
Cú pháp chung:
rsync -a /some/directory/ user@remote:/some/other/directory/
-a là archive option, nó đảm bảo rsync đồng bộ cả subdirectory, file permission, modification time và những thứ tương tự.
Ví dụ đồng bộ directory Pictures ở local sang directory Pictures ở remote:
Ở đây aaron là username, 9.9.9.9 là IP của server, và phần cuối là directory đích trên server đó. Luôn nhớ đặt một dấu gạch chéo ở cuối tên directory.
Điểm hay của công cụ này: lần chạy sau, rsync chỉ copy phần dữ liệu đã thay đổi, bỏ qua dữ liệu cũ vẫn còn giống nhau ở hai nơi. Những lần backup sau vì vậy được tối ưu và mất ít thời gian hơn.
Ở lệnh trên, local directory là nguồn và nó sẽ được tạo lại ở đích remote. Đảo hai vị trí đó thì nguồn thành remote directory và nó được tạo lại trong local directory. rsync cũng dùng được để đồng bộ hai local directory với nhau, không nhất thiết một local một remote.
dd
Thay vì back up file và directory, nếu cần back up trọn một disk hoặc partition thì dùng utility dd. Nó chụp lại kiểu như một bức ảnh của toàn bộ dữ liệu trên disk hay partition đó: một bản copy chính xác từng bit, nên việc này còn được gọi là imaging.
Trước khi lưu image của một disk hoặc partition, nên unmount disk hay partition đó, để chắc chắn không có dữ liệu nào bị thay đổi trong lúc đang back up.
sudo dd if=/dev/sda of=/home/aaron/sda.img bs=1M status=progress
Giải thích từng phần:
- Cần quyền root để truy cập mọi dữ liệu trên disk, nên lệnh chạy qua
sudo. iflà input file: path tới device của disk hoặc partition.oflà output file: path tới file đầu ra, nơi muốn lưu image.bslà block size, nên chỉ định một megabyte hoặc lớn hơn, vì nó làm quá trình nhanh hơn. Không chỉ định thì block size mặc định nhỏ hơn nhiều, dẫn tới các phép read và write kém hiệu quả.status=progressbảoddhiện tiến độ nó đang làm.
Sau này muốn khôi phục disk image từ file trở lại disk thì đảo hai option if và of:
sudo dd if=/home/aaron/sda.img of=/dev/sda bs=1M status=progress
Lưu ý một chuyện: đừng chạy lệnh khôi phục này trên virtual machine đang dùng để học, vì nó sẽ ghi đè lên virtual disk của máy đó.
8. Redirect input và output
Để hiểu input và output redirection, nhìn vào một utility như sort. Trước tiên, sort mong đợi nhận vào một ít text input, và thường nó lấy input đó từ một file.
Giả sử có file.txt với một ít nội dung. Chạy lệnh sau thì sort lấy dữ liệu input từ file.txt, sắp xếp lại cho đúng thứ tự, rồi sinh ra text output là kết quả đã sắp:
sort file.txt
Rất nhiều utility của Linux làm việc theo kiểu này. Vậy chuyện input output redirection để làm gì?
Redirect output ra file
sort đã sắp xếp đúng các con số, nhưng rồi nó chỉ hiển thị kết quả lên màn hình. Muốn lưu lại kết quả đã sắp thì dùng output redirection:
sort file.txt > sortedfile.txt
Output lần này không hiện lên màn hình rồi mất đi, mà được lưu vào sortedfile.txt. Text output đã được chuyển hướng tới file đó. File không nhất thiết phải tồn tại trước: redirection tự tạo nó ra.
Vì sao gọi là redirection? Vì program có một vị trí mặc định để gửi output, bình thường là cửa sổ terminal trên màn hình. Vị trí mặc định đó bị thay đổi, output được dẫn sang nơi khác, nên gọi là output redirection.
Để redirect output ra file, thêm một dấu lớn hơn vào cuối lệnh rồi ghi file đích muốn lưu nội dung vào.
Ghi đè và ghi tiếp
Redirect theo cách trên thì file bị ghi đè. Thử chạy một lệnh có output rồi redirect vào cùng một file, năm lần liên tiếp, sẽ thấy chỉ output của lần cuối còn lại trong file. Mỗi lần redirect bằng một dấu lớn hơn, nội dung cũ mất đi và nội dung mới được ghi vào.
Có lúc như vậy không ổn. Đổi hành vi bằng cách dùng hai dấu lớn hơn, khi đó output mới được ghi thêm vào cuối thay vì ghi đè:
sort file.txt >> sortedfile.txt
Chạy năm lần sẽ thấy mỗi dòng output mới được thêm vào cuối file. Dòng trên cùng là nội dung cũ nhất, dòng dưới cùng là mới nhất.
Ba stream chuẩn
Dấu lớn hơn redirect output ra file. Cách viết tương đương là thêm số 1 trước ký tự redirection, và kết quả giống nhau:
sort file.txt 1> sortedfile.txt
Nhưng số 1 đó nghĩa là gì? Một program như sort phải biết input của nó đến từ đâu, và phải biết gửi output nó sinh ra đi đâu. Nhưng output có hai loại: một loại dành cho text và dữ liệu thường, đã xử lý thành công; loại kia dành cho error message, warning message, hay bất cứ thứ gì báo hiệu có cái gì đó không ổn.
Khi một utility cần biết input đến từ đâu và output nên đi đâu, nó nhìn vào ba thứ:
- STDIN, viết tắt của Standard Input.
- STDOUT, viết tắt của Standard Output.
- STDERR, viết tắt của Standard Error.
Dấu nhỏ hơn dùng để chỉ standard input, dấu lớn hơn dùng để chỉ standard output. Vì có hai loại output, dấu lớn hơn được thêm tiền tố 1 hoặc 2: tiền tố 1 cho standard output, tiền tố 2 cho standard error.
Giải thích theo cách đơn giản hoá: program hỏi "tôi nên gửi những error này đi đâu", và Linux trả lời "xem standard error đang được đặt thế nào thì gửi tới đó". Nên viết 2> errors.txt là đặt standard error trỏ tới file đó, và application biết cần dẫn error của nó đi đâu.
Tổng kết lại ba dạng:
sort file.txt > file.txt
sort file.txt 1> file.txt
sort file.txt 2> errors.txt
Vì sao cần redirect error?
Thử lại mấy lệnh find và grep ở các phần trước sẽ thấy chúng in ra rất nhiều lỗi Permission denied. Error message bình thường rất hữu ích, nhưng ở đây chúng chỉ cản đường, làm khó nhìn ra kết quả thật.
grep -r 'password' /etc 2> /dev/null
Output giờ sạch, chỉ còn đúng kết quả cần tìm, không còn error message. Phần phép thuật nằm ở đoạn cuối: số 2 cho biết đây là standard error, nơi error message và warning được gửi tới; dấu lớn hơn nghĩa là muốn redirect nó; và /dev/null là nơi được chọn để redirect standard error tới.
Như vậy error message bị dẫn ra khỏi màn hình, vào /dev/null, lỗ đen của Linux. Thứ gì gửi vào /dev/null cũng bị loại bỏ. File đặc biệt /dev/null này hay được dùng cho đúng mục đích đó: tống đi những output không cần thiết.
Redirect cả hai loại output
Redirect output thường và output lỗi vào hai file khác nhau cùng lúc cũng được. Ghi đè nội dung cũ:
grep -r 'password' /etc 1> output.txt 2> errors.txt
Ghi thêm nội dung mới vào cuối file mà giữ nguyên nội dung cũ, chỉ khác ở chỗ một dấu lớn hơn hay hai dấu lớn hơn:
grep -r 'password' /etc 1>> output.txt 2>> errors.txt
Nhìn lại output của lệnh grep, message thường và error message nằm trộn lẫn với nhau. Muốn lưu toàn bộ output đúng như đang thấy trên màn hình thì dùng lệnh sau, với phần 2>&1 đặt ở cuối:
grep -r 'password' /etc > alloutput.txt 2>&1
Cách này đảm bảo mọi thứ thấy trên màn hình, gồm cả standard output lẫn error message, đều được redirect vào một file.
Nếu khó nhớ thứ tự đặt hai quy tắc redirect này thì dùng lệnh thay thế sau, trong đó thấy số 1 rồi tới số 2, đúng thứ tự tự nhiên của hai con số, nên dễ đặt đúng chỗ:
grep -r 'password' /etc 1> alloutput.txt 2>&1
Chuyện gì xảy ra ở đây? Phần 1> alloutput.txt nói rằng standard output đi vào file alloutput.txt. Phần thứ hai nói rằng standard error đi vào standard out, và trong trường hợp này &1 chỉ thị cho lệnh redirect tới standard out thay vì tới một file. Nhớ rằng &1 là standard out, &2 là standard error. Với standard error chảy vào standard out, error message trộn với message thường, và cả hỗn hợp đó đi vào alloutput.txt.
Đặt sai thứ tự, tức để 2>&1 ở vị trí đầu, thì chuyện khác xảy ra: nó redirect error tới vị trí hiện tại của standard out, mà vị trí đó vẫn là màn hình, vì standard out tại thời điểm đó chưa bị redirect. Kết quả là error không vào file alloutput.txt mà hiện lên màn hình rồi mất đi.
Redirect input
Những lệnh như sort đã biết phải lấy input từ file.txt. Nhưng có một tình huống đáng dùng input redirection.
Hình dung một lệnh tưởng tượng kiểu send-email [email protected]. Mặc định nó mong đợi input từ bàn phím để người dùng tự gõ nội dung email. Nó không cho phép truyền một file vào làm input như sort. Với input redirection thì lừa được nó, làm như thể nội dung đó được gõ từ bàn phím:
Dấu nhỏ hơn redirect input từ file vào program. Hiệu ứng giống y như đã gõ tay toàn bộ những gì lưu trong email-content.txt. Trường hợp cần redirect input hiếm gặp, nhưng khi không truyền được file vào một utility theo cách thông thường thì đây là hướng đáng thử.
Here-document và here-string
Có một tình huống dùng input redirection hay gặp hơn, lần này không phải từ file. Nó được gọi là here-document, viết tắt là here-doc.
Với một thứ như EOF, lệnh được báo rằng input cần truyền vào kết thúc ngay trước dòng cuối cùng có chữ EOF. Chữ nào cũng dùng được thay cho EOF, có thể gõ END hay DELIMITER hay gì cũng được, nhưng EOF là lựa chọn thông dụng.
sort << EOF
3
1
2
EOF
Cách này cho phép gõ nhiều dòng text để truyền vào làm dữ liệu input cho lệnh. Lợi thế của nó: cả lệnh và input mà lệnh cần xử lý được gộp vào một khối text duy nhất, dễ copy, dễ paste, dễ chia sẻ trên internet.
Một biến thể gần giống là here-string, dùng để truyền một dòng, một chuỗi, làm input cho program. Thứ gì đi sau ba dấu nhỏ hơn chính là input:
bc <<< "2+3"
# Output:
5
Ở đây lợi ích dễ thấy hơn. Bình thường bc là một máy tính command line: mở nó ra, bước vào môi trường của nó, rồi gõ biểu thức toán vào. Cách đó buộc phải đi qua nhiều bước: gõ lệnh để mở bc, gõ biểu thức toán, rồi thoát bc, có lúc khá bất tiện. Với here-string, input truyền được vào mà không cần mở utility rồi dùng bàn phím gõ vào bc. Những thứ như vậy hữu ích trong script, nơi cần tự động giải một biểu thức toán và lấy kết quả, trong khi không có input từ bàn phím.
Pipe
Phần lớn utility của Linux rất nhỏ và thường chỉ làm một loại việc. Lấy hai trong số đó: grep để tìm, sort để sắp xếp text. Mỗi application chỉ làm được một loại việc nghe có vẻ hạn chế, nhưng có một ví dụ rõ ràng.
Tìm mọi dòng chưa bị comment trong một file để biết những setting nào đang có hiệu lực, với file nhỏ thì đọc dễ. Nhưng với một file có cả nghìn setting và cả nghìn dòng text, phải cuộn lên cuộn xuống liên tục để xem những thứ đang quan tâm. Chúng nằm không theo thứ tự nào, nên không biết CREATE_HOME hay ENCRYPT_METHOD nằm ở đâu, ở dòng số bao nhiêu.
Nếu sắp được mọi biến ở đây theo thứ tự chữ cái thì dễ hơn nhiều: cuộn lên đầu là tới chỗ CREATE_HOME, cuộn xuống một chút là tới ENCRYPT_METHOD. Hai cái đó sẽ nằm gần nhau, vì thứ tự chữ cái là A, B, C, D, E, nên ENCRYPT ngay sau những gì bắt đầu bằng chữ D.
Nhưng grep chỉ tìm được chứ không sắp xếp được output. Đã có sort cho việc sắp xếp, vậy chỉ cần gửi output của grep thẳng sang sort. Việc đó làm được bằng dấu pipe, và gọi là piping output từ program A sang program B trên command line:
grep -v '^#' /etc/login.defs | sort
Giờ các cột lại lệch nhau, cái thò ra bên phải, cái lùi vào bên trái. Cách xử lý là pipe tiếp sang một program nữa. Có thể thêm bao nhiêu dấu pipe cũng được:
grep -v '^#' /etc/login.defs | sort | column
Với grep tìm ra text, pipe output sang sort để sắp xếp theo thứ tự chữ cái, rồi pipe từ sort sang utility column để xếp mọi cột cho thẳng hàng. Mỗi program nhận output từ program bên trái, làm việc trên đó, rồi gửi sang program bên phải.
Piping output vì vậy rất đáng giá: nó cho phép nối nhiều program lại với nhau và làm được những việc phức tạp hơn nhiều so với khả năng của một utility đơn lẻ.
Nguồn tham khảo
Nguồn gốc: Khóa "Prep Course - Linux Foundation Certified System Administrator (LFCS) Certification" — phần "Essential Commands" (tìm text với grep, regular expression cơ bản và extended regular expression, archive với tar, nén và giải nén với gzip, bzip2, xz và zip, backup với rsync và dd, cùng input output redirection và pipe), nền tảng KodeKloud. Giảng viên: Jeremy Morgan và Alexandru Andrei.