Tìm hiểu các shell và so sánh các sell
Ở trong buổi học này, chúng tôi sẽ trình bày vài điều cơ bản trong việc sử dụng bash như một ngôn ngữ kịch bản và giới thiệu một số công cụ của shell được sử dụng thường xuyên trên môi trường dòng lệnh trong các công việc hằng ngày của bạn. Show Shell Scripting - Ngôn ngữ kịch bản ShellChúng ta đã làm quen với việc thực hiện các lệnh bằng shell (vỏ) và pipe (liên kết) chúng lại với nhau thành một quy trình. Tuy nhiên, trong một vài trường hợp, bạn sẽ cần phải thực thi hàng loạt câu lệnh và sử dụng các cấu trúc điều khiển như câu điền kiện hoặc vòng lặp. Ngôn ngữ shell là bước tiếp theo để có thể sử dụng những thứ phức tạp hơn. Hầu hết các shell đều có một ngôn ngữ kịch bản riêng với những cú pháp riêng biệt để tương tác với biến, cấu trúc điều khiển.Điều đặc biệt khiến ngôn ngữ shell khác biệt khi so sánh chúng với các ngôn ngữ kịch bản khác chính là ngôn ngữ shell đã được tối ưu cho việc thực thi các tác vụ liên quan tới shell (ở môi trường dòng lệnh). Do đó, việc tạo quy trình cho lệnh (pipe), lưu kết quả vào file, đọc dữ liệu từ thiết bị nhập chuẩn là những thứ nguyên thuỷ trong khi viết shell, điều này khiến shell script dễ dàng để sử dụng hơn là những ngôn ngũ kịch bản tổng quát. Ở trong phần này chúng ta sẽ sử dụng bash để lập trình shell vì bash rất phổ biến. Ghi chú (người dịch):
Để gán giá trị cho biến bằng bash, sử dụng cú pháp
1 và truy cập giá trị của biến bằng cú pháp
2. Lưu ý
3 sẽ không chạy bởi vì câu lệnh sẽ được biên dịch thành việc gọi chương trình
4 với đối số là
5 và
6. Tóm lại, dấu khoảng cách đóng vai trò dấu phân cách các đối số trong các ngôn ngữ shell. Việc này có thể sẽ hơi khó hiểu và gây nhầm lẫn ở giai đoạn đầu, nên hãy luôn kiểm tra việc này. Chuỗi có thể được khai báo bằng dấu
7 và
8 trong bash, nhưng chúng không bằng nhau. Chuỗi được khai báo bằng
7 là chuỗi theo nghĩa đen (giá trị cụ thể) và sẽ không được thay thế bằng các giá trị của biến, trong khi chuỗi được khai báo bằng
8 thì có.
Như hầu hết các ngôn ngữ lập trình khác, bash cũng hỗ trợ các cấu trúc điều khiển như
1,
2,
3 và
4. Và tất nhiên,
5 hỗ trợ các hàm nhận vào các đối số và thực hiện các tác vụ. Đây là một ví dụ về hàm có chức năng là tạo một thư mục và
6 vào nó.
Ở đây
7 mang ý nghĩa là đối số được truyền vào đầu tiên cho hàm. Không như các ngôn ngữ khác,
5 sử dụng một tập hợp các biến đặc biệt để chỉ đến các đối số, mã lỗi và các biến liên quan. Ở dưới là một danh sách của chúng. Các bạn có thể tìm một danh sách đầy đủ và chi tiết hơn ở đây.
Các lệnh thường sẽ trả về kết quả ở
2, lỗi ở
3, và một mã kết quả phục vụ cho mục đích lập trình. Mã kết quả hoặc mã kết thúc (return code or exit status) là cách để các đoạn mã/lệnh tương tác với nhau nhằm xác định việc thực thi như thế nào. Giá trị của mã kết thúc là 0 biểu hiện mọi thứ vẫn bình thường, khác 0 nghĩa là có lỗi xảy ra. Mã kết thúc có thể được sử dụng để xử lý điều kiện thực hiện các lệnh tiếp theo bằng việc sử dụng toán tử
4 và
5 (toán tử
6 và
7 ), cả 2 điều là toán tử short-circuiting. Các lệnh có thể được ngăn cách với nhau trên cùng một dòng sử dụng dấu
8 (semicolon-chấm phẩy). Lệnh
9 sẽ luôn trả về mã kết thúc là 0 và lệnh
0 sẽ luôn trả về mã kết thúc là 1. Ghi chú (người dịch):
Xem một vài ví dụ sau:
Một ứng dụng khác là lưu trữ giá trị đầu ra của một lệnh vào một biến. Việc này có thể thực hiện bằng việc sử dụng command substitution. Bất cứ khi nào bạn sử dụng cú pháp
2, shell sẽ thực thi lệnh
3 và sau đó lấy kết quả được trả về và thay thế tại chỗ. Ví dụ, đối với lệnh sau
4, shell thưc thi lện
5 trước và sau đó mới thực hiện lặp qua từng giá trị. Một ứng dụng tương đương nhưng ít phổ biến hơn là process substitution,
6 sẽ thực thi lệnh
3 và lưu trữ giá trị của kết quả vào một file tạm thời và thay thế bằng tên file tạm thời đó. Điều này rất hữu dụng khi những lệnh đọc dữ liệu từ file thay vì thiết bị nhập chuẩn STDIN. Ví dụ
8 sẽ chỉ ra những files khác nhau trong 2 thư mục
4 và
6. Bỏi vì có quá nhiều thông tin, hãy xem các ví dụ cụ thể kỹ hơn. Đoạn mã sẽ lặp qua các đối số được cung cấp, thực thi lệnh
1 để so sánh đối số với chuỗi
2, và thêm chuỗi
2 này vào file như là 1 dòng comment nếu trong file không có từ bất kỳ chuỗi
2.
Câu lệnh điều kiện kiểm tra giá trị của biến
5 khác 0. Bash đã cài đặt nhiều lệnh so sánh - tham khảo ở danh sách đầy đủ ở trang web manpage dành cho
6. Khi thực hiện lệnh so sánh, cố gắng sử dụng 2 cặp dấu ngoặc vuông
7 thay vì chỉ 1
8.Điều này sẽ giảm tỉ lệ lỗi xuống mặc dù điều này không tương thích với
9. Giải thích chi tiết có thể tìm ở đây. Khi mã nguồn được thực thi, việc truyền nhiều đối số tương tự nhau khá phổ biến. Bash cung cấp cách để làm cho mọi chuyện đơn giản hơn, khả năng expanding expressions (mở rộng biểu thức) bằng việc gom nhóm các filename expansion (định dạng mở rộng của file). Kỹ thuật này được gọi là shell globbing.
Chú thích (người dịch):
Viết mã
5 đôi khi khó khăn và không trực quan. Có một vài công cụ như shellcheck giúp chúng ta tìm lỗi ở trong mã sh/bash của bạn. Lưu ý rằng mã không nhất thiết phải được viết ở bằng bash thì mới có thể chạy được ở giao diện dòng lệnh. Đây là một đoạn mã Python sẽ trả về kết quả là các tham số được truyền vào với thứ tự nghịch đảo.
Kernel biết thực thi một đoạn mã sử dụng trình biên dịch của python thay vì shell, bởi vì chúng ta đã đính kèm dấu shebang ở đầu file script. Đây là một ví dụ tốt để viết
4 bằng lệnh
5 để có thể giải quyết trường hợp nơi lưu trữ cách lệnh trong hệ thống, tằng cường khả năng di động của mã. Để xử lý vị trí,
5 sẽ sử dụng biến môi trường
7 đã được giới thiệu ở bài đầu tiên. Ví dụ, dòng
4 sẽ trông như thế này
9. Một vài điều khác biệt giữa function (hàm) và script (mã) của shell cần ghi nhớ:
Công cụ của shellCách sử dụng lệnhTại thời điểm này, bạn có thể đang tự hỏi rằng làm thế nào để tìm các tuỳ chọn (flags) dành cho các lệnh giống như
1,
2 and
3. Tổng quát hơn, đối với một lệnh, làm sao ta có thể tìm ra các chức năng của chúng và cái tuỳ chọn tương ứng? Chúng ta có thể bắt đầu tìm google cho câu hỏi này, tuy nhiên UNIX thì có trước cả StackOverflow, và có 1 cách truyền thống để lấy các thông tin này. Như chúng ta đã biết ở trong bài đầu tiên, cách tiếp cận đầu tiên là gọi lệnh với với tuỳ chọn
4 hoặc
5. Cách tiếp cận chi tiết hơn là sử dụng lệnh
6. Viết ngắn gọn của từ
7,
6 cung cấp 1 trang hướng dẫn sử dụng (gọi là manpage) cho một lệnh bất kỳ. Ví dụ
9 sẽ trả về tất cả các tác vụ của lệnh
00 cùng với các tuỳ chọn mà lệnh cung cấp, cùng với tuỳ chọn
01 đã được giới thiệu trước đó. Thực tế, những đường link tôi đã đính kèm cho các lệnh thực chất một phiên bản trực tuyến của Linux manpage. Thậm chí những lệnh không có sẵn mà bạn cần phải cài đặt cũng có một trang manpage nếu lập trình viên có viết chúng và đóng gói chúng cùng quá trình cài đặt. Đối với những công cụ tương tác trực tiếp ví dụ như những thứ dựa trên
02, hướng dẫn sử dụng của các lệnh thường được truy cập ngay trong chương trình bằng việc viết
03 hoặc
0. Đôi khi các trang manpages có thể cung cấp rất nhiều thông tin chi tiết mô tả về lệnh, khiến nó trở lên khó để xác định xem những tuỳ chọn/ cú pháp nào để sử dụng trong các trường hợp cơ bản.TLDR pages là một giải pháp đơn giản, tập trung vào việc đưa ra các ví dụ cụ thể và trường hợp sử dụng của từng lệnh giúp bạn nhanh chóng nhận ra các tuỳ chọn nào cần thiết. Cụ thể hơn, tôi thường hay sử dụng trang
05 cho các lệnh
06 và
07 hơn là manpages. Finding filesMột trong các công việc thường xuyên lặp đi lặp lại đối với lập trình viên là tìm files hoặc thư mục. Các hệ thống dựa trên UNIX thường có sẵn lệnh
08, một công cụ tuyệt vời của shell để tìm kiếm file.
08 sẽ tìm kiếm đệ quy các file có tên khớp với một vài tiêu chuẩn nào đó.
Ngoại trừ các thứ được kể trên,
08 có thể thực hiện nhiều tác vụ trên nhiều file là kết quả của câu truy vấn. Đặc điểm này thực sử hữu dụng để đơn giản hoá những công việc nhàm chán
Mặc dù sự phổ biến của
08, cú pháp của nó thực sự khó ghi nhớ. Ví dụ, để thực thiện công việc đợn giản như là tìm kiếm các file khớp với mẫu
12, bạn phải viết câu lệnh như thế này
13 (hoặc
14 nếu muốn không phân biệt chữ hoa, chữ thường). Bạn có thể bắt đầu viết một vài bí danh (alias) cho những trường hợp trên, nhưng tư tưởng của shell khuyến khích tìm kiếm những thứ thay thế tốt hơn. Luôn nhớ rằng, một trong những thuộc tính thú vị nhất của shell là bạn chỉ đang gọi những chương trình mà thôi, cho nên bạn có thể tìm (hoặc thậm chí là viết mới) những thứ thay thế cho cùng 1 công việc. Điển hình,
15 là một chương trình đơn giản, nhanh và dễ sử dụng thay thế cho
08. Chương trình này hỗ trợ một vài thứ mặc định xịn xò như tô màu kết quả, sử dụng biểu thức chính quy (regex - regular expression), và hỗ trợ unicode. Và tất nhiên, theo quan điểm cá nhân của tôi (tác giả), một cú pháp dễ hơn. Một trường hợp cụ thể, cú pháp để tìm một mẫu như
12 là
18 Hầu hết các ý kiến đều đồng ý rằng
08 và
15 rất tốt, nhưng chắc một vài người sẽ tự hỏi rằng về độ hiệu quả của việc tìm kiếm so với việc đánh chỉ mục hoặc sử dụng cơ sở dữ liệu cho việc tìm kiếm nhanh. Và đây là điều mà
21 phát triển.
21 sử dụng cơ sở dữ liệu được cập nhật sử dụng
23. Trong hầu hết hệ thông,
23 được cập nhật hàng ngày thông qua
25. Bởi vì lý do này nên có một sử đánh đổi giữa tốc độ và độ trực tuyến (fresness). Một điều nữa là
08 và các công cụ tương tự thì có thể tìm kiếm file dựa trên nhiều thuộc tính khác như kích cỡ, ngày chỉnh sửa, quyền hạn, trong khi
21 chỉ sử dụng tên file. So sánh chi tiết có thể tìm ở đây. Finding codeTìm kiếm file bằng tên khá hữu ích, tuy nhiên thao tác tìm kiếm dựa trên nội dung file cũng cần thiết không kém. Một trường hợp phổ biến là tìm tất cả các file chứa nhiều hơn một mẫu, cùng với nơi mà kết quả xuất hiện (số dòng). Để làm việc này, hầu hết hệ thống lõi UNIX đều cung cấp
1, một công cụ giúp tìm kiếm các mẫu dựa trên văn bản đầu vào.
1 là một công cụ cực kỳ mạnh mà chúng ta sẽ tìm hiểu thêm, chi tiết về nó ở bài sau (data wrangling). Ở hiện tại,
1 cung cấp rất nhiều tuỳ chọn (flags) khiến nó trở nên linh hoạt. Một vài tuỳ chọn tôi hay dùng là
31 để lấy ngữ cảnh kết quả và
32 để nghịch đảo kết quá, ví dụ, tìm những đoạn không khớp với mẫu. Ví dụ
33 sẽ in ra 5 dòng trước và sau kết quả. Khi cần thực hiện việc tìm kiếm nhiều files, bạn có thể sử dụng
34 bởi vì tuỳ chọn này sẽ đi vào từng file ở trong thư mục và tìm kiếm kết quả. Nhưng
35 có thể được cải tiến bằng nhiều cách, ví dụ như bỏ qua
36 folded, sử dụng CPU đa nhân để hỗ trợ, et cetera Có nhiều công cụ thay thế
1 dã được phát triển, bao gồm ack, ag và rg. Tất cả những công cụ trên rất tuyệt vời và cung cấp cùng chức năng. Hiện tại tôi đang sử dụng ripgrep
38, bởi vì nó rất nhanh và dễ. Ví dụ:
Lưu ý rằng với
08/
15, điều quan trọng là các vấn đề này có thể được giải quyết rất là dễ dàng, còn việc sử dụng công cụ nào thì không quan trọng. Finding shell commandsChúng ta tìm hiểu việc tìm kiếm các files và code, khi bắt đầu sử dụng shell nhiều hơn, bạn có thể muốn tìm các lệnh mà đã gõ ở vài thời điểm. Điều đầu tiên cần phải biết đó là mũi tên lên sẽ trả về bạn lệnh cuối cùng, và nếu bạn giữ nó thì nó sẽ từ từ duyệt qua lịch sử shell của bạn. Lệnh
41 sẽ giúp bạn truy cập tới lịch sử của shell . Nó sẽ in ra thiết bị xuất chuẩn lịch sử của shell. Nếu bạn muốn tìm kiếm lịch sử, ta có thể nối đầu ra với
1 để thực hiện việc tìm kiếm.
43 sẽ in ra các lệnh có chứa chuỗi “find”. Trong hầu hết các shell, bạn có thể sử dụng
44 để thực hiện việc tìm kiếm lịch sử. Sau khi nhấn phím
44, bạn có thể nhập một chuổi mà bạn muốn tìm kiếm ở trong lịch sử. Và nếu bạn tiếp tục giữ phím, bạn sẽ đi vào vòng lặp các kết quả. Điều này cũng có làm được bằng việc nhấn phím UP/DOWN (mũi tên lên/xuống) đối với zsh Một điều thú vị là
44 là sử dụng .
47 là một công cụ tìm kiếm tổng quát có thể được sử dụng với rất nhiều lệnh khác. Do đó, nó được sử dụng để tìm kiếm nhanh chóng dựa trên lịch sử của bạn và hiển thị kết quả một cách tiện lợi và dễ nhìn. Một thứ hay ho về lịch sử shell nữa mà tôi rất thích đó là gợi ý tự động dựa trên lịch sử (history-based autosuggestion). Lần đầu được giới thiệu bởi fish, tính năng này sẽ giúp bạn hoàn thành lệnh shell hiện tại dựa trên lịch sử các lệnh gần nhất có các điểm chung. Tính năng này có thể được kích hoạt ở zsh và nó sẽ giúp cuộc đời bạn dễ dàng hơn khi làm việc với shell. Bạn cũng có thể tuỳ chỉnh các hành vi của lịch sử, ví dụ chặn các lệnh có các dấu khoảng trắng dư thừa. Điều này sẽ có ích khi nhập mật khẩu hoặc các thông tin nhạy cảm khác. Để kích hoạt chức năng này, thêm
48 vào file
49 hoặc
50 vào file
51. Nếu có sai sót không thêm khoảng cách thừa, bạn có thể xoá thủ công các chúng ở trong
52 hoặc
53 Directory NavigationỞ đoạn trước, chúng ta đã mặc định rằng chúng ta đang ở đúng nơi cần thực hiện các tác vụ đó. Tuy nhiên làm thể để nào để di chuyển nhanh chóng giữa các thư mục. Có rất nhiều cách đơn giản cho bạn thử, ví dụ như viết một alias hoặc tạo symlink sử dụng ln -s, nhưng thực tế rằng là các lập trình viên tìm ra cách thông minh và đầy chất xám hơn tại thời điểm hiện tại. Với phương châm của khoá học này, bạn sẽ đối mặt với những tình huống phổ biến nhất. Tìm những file và/hoặc thư mục thường xuyên được sử dụng có thể được được xử lý bằng
54 và
55. Fasd xếp hạng các files và thư mục dựa trên frecency, nghĩa là , cả 2 tần số và gần đây. Mặc định,
54 thêm một lệnh
57 mà bạn có thể dủng để có thể nhanh chóng
6 chỉ với một chuỗi của thự mục frecent. Ví dụ Nếu bạn thường xuyên di chuyển tới
59 thì bạn chỉ đơn giản là sử dụng
60 để di chuyển tới đó. Sử dụng
55 thì cú pháp tương tự sẽ là
62. Một vài công cụ giúp bạn nhanh chóng nắm bắt được cấu trúc thư mục :
63,
64 thậm chí là một trình quản lý file toàn diện như
65 hoặc
66. Exercises
Như chúng ta đã đề cập ở trên thì tuỳ chọn
77 của
08 thực sự rất mạnh mẽ trong việc thực thi các tác vụ trên file chúng ta cần tìm kiếm. Tuy nhiên, nếu chúng ta muốn làm điều gì đó với toàn bộ file, ví dụ như là nén lại thành một file zip? Dựa vào các kiến thức trên, Lệnh sẽ nhận đầu vào từ các đối số và cả STDIN. Khi piping các lệnh, chúng ta thực chất đang kết nối STDOUT với STDIN, nhưng một vài lệnh như
06 lại nhận đầu vào từ những đối số (nhận tên file); Để tạo cầu nối cho sự bất tiện này,
80 sẽ giúp chúng ta thực thi lệnh sử dụng STDIN như đối số. Ví dụ,
81 sẽ xoá tất cả các file ở thư mục hiện hành. Công việc của bạn là viết một lệnh tìm kiếm đệ quy tất cả các HTML files trong các folder và tạo một file zip. Lưu ý rằng lệnh bạn viết phải hoạt động cả với những file có dấu khoảng trắng trong tên (gợi ý: tìm hiểu vể
82 của
80) Nếu bạn sử dụng macOS, lưu ý rằng lệnh
08 mặc định của BSD thì khác với cái của GNU coreutils. Bạn có thể sử dụng
85 đối vối
08 và
87 đối với
80. Là một người sử dụng macOS thì bạn phải lưu ý rằng những tiện ích của môi trường giao diện dòng lệnh khác với của GNU, bạn cũng có thể cài đặt phiên bản của GNU trên macOS nếu muốn bằng việc sử dụng brew |