Tab Shell của Bài 04 nói terminal chỉ chuyển chữ còn shell mới hiểu chữ, rồi đi thẳng vào các lệnh. Phụ lục này quay lại chỗ đó. Nó không thêm lệnh nào mới, chỉ giải thích những gì xảy ra phía sau mấy lệnh bạn đã gõ, và muốn hiểu thì chỉ cần ba điều về process đã gặp ở Bài 03:
- process cha sinh ra process con;
- mỗi process có một thư mục làm việc;
- mỗi process giữ một bản sao biến môi trường riêng.
Vì sao lại là hai chương trình
Gõ lệnh thì chỉ thấy một cửa sổ, sao lại phải tách thành terminal và shell? Vì ngày xưa chúng nằm ở hai nơi khác nhau. Máy tính thì đặt trong phòng máy, còn thứ để trên bàn người dùng chỉ là một thiết bị nhập và xuất, nối dây về máy tính đó. Thiết bị ấy gọi là terminal, và Ảnh 1 là hai thế hệ của nó.


tty, viết tắt của teletypewriter.Terminal thời đó không chạy chương trình nào cả. Nó gửi phím bạn gõ theo dây về máy tính, và in ra những chữ máy tính gửi lại. Chương trình đọc các phím đó và hiểu chúng là lệnh thì chạy trên máy tính, và đó là shell.
Terminal ngày nay là chương trình giả lập những thiết bị đó (terminal emulator), ví dụ Windows Terminal hay cửa sổ của Git Bash. Cách chia việc thì vẫn như cũ: terminal chuyển chữ, còn shell hiểu chữ.
Một terminal, nhiều shell
Chia việc như vậy thì được gì? Được chỗ này: terminal không cần biết bên trong nó đang chạy shell nào. Vì vậy cùng một cửa sổ Windows Terminal mở được cmd.exe, PowerShell hay bash, và đổi shell thì ngôn ngữ của lệnh đổi theo.
Mỗi shell hiểu dòng lệnh theo cách của nó, nên cùng một dòng mà gõ vào shell khác thì có thể thành việc khác hẳn.
Ví dụ chữ time trong bash là từ khoá đo thời gian, còn trong cmd.exe nó lại là lệnh xem và
đặt giờ hệ thống. Đây là output thật khi phép đo của Bài 01 bị gõ nhầm vào cmd.exe:
> time curl -4 -o /dev/null http://127.0.0.1:9999
The system cannot accept the time entered.
Enter the new time: 2
A required privilege is not held by the client.
time là một giờ mới. Nó từ chối, hỏi lại, rồi báo thiếu quyền, vì đặt giờ hệ thống cần quyền quản trị.Bản thân lệnh không sai, chỉ là một câu đúng cú pháp bash đã bị đọc theo cú pháp của cmd.exe. Vì vậy khi một lệnh chép từ tài liệu về mà báo lỗi lạ, việc đầu tiên nên xem là mình đang gõ vào shell nào.
Bạn gõ ls -l /etc rồi bấm Enter, và một danh sách hiện ra. Giữa hai việc đó bash đã làm những gì?
Bảy bước của một dòng lệnh
Bash làm bảy bước, vẽ ở Hình 2, và lặp lại đúng bảy bước đó cho mọi dòng bạn gõ.
bash (PID 812) kernel
───────────────────────────────────── ──────────────────────────────
1 in dấu nhắc ten@may:~$
2 đọc dòng ls -l /etc
3 tách chữ ls -l /etc
4 tìm "ls" không nằm sẵn trong bash
tìm theo PATH: /usr/bin/ls
5 fork, rồi exec ──────────────────► tạo process con PID 913
chạy /usr/bin/ls
6 đợi ◄────────────────── PID 913 thoát, exit status 0
7 ghi 0 vào $?, quay lại bước 1
Bước 3 cắt dòng lệnh thành các chữ theo dấu cách. Muốn giữ nguyên một cụm có dấu cách thì bọc nó trong dấu nháy,
như grep -i "killed process" ở Bài 03. Nếu không có dấu nháy thì grep nhận hai chữ rời nhau, và nó hiểu
chữ thứ hai là tên một file cần tìm trong đó.
Bước 5 là chỗ nối với Bài 03. Bash không tự chạy ls bên trong mình. Nó nhờ kernel nhân bản chính nó
thành một process con, rồi process con đó thay chương trình của mình bằng ls. Vì thế lệnh nào bạn gõ cũng
là con của bash, và mang theo bản sao biến môi trường của bash.
Bước 6 và 7 là chỗ sinh ra exit status mà Bài 04 dùng để đọc 143 và 137. Bash đứng đợi process con, và khi process con thoát thì kernel báo lại cho bash con số nó để lại. Nếu process con chết vì một signal thì kernel báo số của signal đó, và bash tự đổi thành 128 cộng số signal.
Cả bức tranh trong một hình vẽ
Ảnh 2 vẽ những thứ vừa kể thành một toà nhà: terminal, process, và kernel là bên trông coi tất cả.
| mà Bài 04 dùng trong ps -ef | grep java. Con đứng ở cửa số 80 là một web server đang listen port 80. Cái bàn có nhãn process table là một trò chơi chữ: table vừa là bàn, vừa là bảng process mà kernel giữ. Tranh đặt mọi process vào trong kernel cho dễ nhìn. Trên máy thật thì process chạy ở user space, còn kernel là bên giữ bảng, cửa và đường ống. Các nhãn còn lại (cron, watchdog, filesystem, cửa 22) nằm ngoài phụ lục này.Ở bước 4 của Hình 2, bash phải trả lời một câu: chữ ls ứng với file nào trên đĩa?
PATH là một danh sách thư mục
Bash tìm câu trả lời trong biến môi trường PATH. PATH là một danh sách thư mục, ngăn nhau bằng dấu
hai chấm. Bash tìm lần lượt từng thư mục, và dùng file tên ls ở thư mục đầu tiên có nó. Vì PATH là biến
môi trường nên đúng như Bài 03 đã nói, mỗi process mang một bản sao, và process con nhận bản sao từ cha.
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:…:/mnt/c/WINDOWS/system32:…
$ which ls
/usr/bin/ls
$ type cd
cd is a shell builtin
$ khongcolenhnay
khongcolenhnay: command not found
which cho biết bash
sẽ dùng file nào, còn type cho biết một cái tên là loại lệnh gì. Trong WSL, cuối PATH có thêm thư mục của
Windows, nên từ Ubuntu vẫn gọi được chương trình Windows.Đọc lại lỗi command not found
Vậy lỗi command not found nghĩa là gì? Nghĩa là không thư mục nào trong PATH có file mang tên đó. Bài
04 nói gọn là "máy chưa có chương trình tên đó", còn nói cho đủ thì có ba lý do: gõ sai tên, chưa cài, hoặc đã cài
nhưng ở một chỗ nằm ngoài PATH.
Lý do thứ ba là lý do hay làm mất thời gian nhất, vì chương trình có thật trên đĩa mà gõ tên vẫn không chạy. Gặp
chuyện đó thì có hai cách. Một là gõ thẳng đường dẫn đầy đủ của file, ví dụ /opt/cong-cu/bin/abc, vì đã
ghi đường dẫn thì bash không cần tìm trong PATH nữa. Hai là thêm thư mục chứa nó vào PATH.
Cũng vì bash chỉ tìm trong PATH nên chương trình nằm ngay trong thư mục bạn đang đứng vẫn không chạy được bằng tên
trần. Phải gõ ./abc, trong đó ./ nghĩa là "ở chính thư mục này". Đó là lý do các hướng dẫn
trên Linux hay có dạng ./mvnw hay ./gradlew.
Ở Hình 3, type cd trả lời rằng cd là một shell builtin. Nó khác gì ls,
và vì sao phải khác?
Lệnh nằm sẵn trong bash
Một builtin là lệnh nằm sẵn bên trong bash chứ không phải một file trên đĩa. Gặp builtin thì bash tự làm lấy, không fork và không exec, nên Hình 2 dừng ở bước 4.
Vì sao cd phải là builtin? Lý do đi thẳng từ Bài 03: thư mục làm việc là thuộc tính của từng process.
Giả sử cd là một chương trình riêng. Khi đó bash sẽ chạy nó thành một process con, process con đó đổi thư
mục làm việc của chính nó rồi thoát, còn bash thì vẫn đứng nguyên ở thư mục cũ. Vậy là gõ cd xong
chẳng có gì đổi.
nếu cd là một chương trình riêng cd là builtin
bash đứng ở /home/ten bash đứng ở /home/ten
│ fork, exec │ tự đổi
▼ ▼
cd /etc đổi sang /etc, rồi thoát bash đứng ở /etc
│
▼
bash vẫn đứng ở /home/ten
Vì vậy cd phải do chính bash làm, để đổi thư mục của chính bash. Lệnh export ở Bài 03 là
builtin vì cùng lý do: nó đổi biến môi trường của chính bash, để các process con sinh ra sau đó nhận được bản sao
mới.
Thư mục làm việc và đường dẫn tương đối
Thư mục làm việc của bash thì dùng vào việc gì? Mọi đường dẫn không bắt đầu bằng / đều được nối vào sau
nó. Đó chính là dòng "đường dẫn tương đối tính từ đây" trong Hình 6 của Bài 03. Lấy lệnh
cat ghi-chu.txt làm ví dụ. Đứng ở /home/ten thì nó mở /home/ten/ghi-chu.txt. Sau
khi cd /etc thì vẫn lệnh đó nhưng nhận về No such file or directory, vì giờ nó đi tìm
/etc/ghi-chu.txt.
Lab 5 của Bài 04 có một chỗ dựa vào đúng điều này. Lệnh java -jar target/app-mau.jar dùng một đường dẫn
tương đối, nên phải cd vào thư mục lab/app-mau rồi mới gõ được. Gõ ở chỗ khác thì Java báo
không tìm thấy file jar.
Gom lại
| Thứ | Làm gì | Lỗi hay gặp |
|---|---|---|
| Terminal | Chuyển phím gõ vào, hiện chữ ra | Không sinh lỗi về lệnh |
| Shell | Hiểu dòng lệnh theo cú pháp của nó | Lệnh bash gõ vào cmd.exe thành việc khác |
| PATH | Nơi bash tìm chương trình theo tên | command not found |
| fork và exec | Chạy chương trình thành một process con | Process con không đổi được gì của bash |
| Thư mục làm việc | Gốc để tính đường dẫn tương đối | No such file or directory |
| Exit status | Con số process con để lại cho bash | Khác 0 là thất bại, trên 128 là chết vì signal |
Nối về bài chính
Bài 04 cần bạn gõ được chừng chục lệnh để chạy và giết app mẫu, nên nó chỉ nói mỗi lệnh làm gì. Phụ lục này cho
thấy phía sau mỗi lệnh đó là cùng một vòng bảy bước, và ba lỗi hay gặp nhất khi gõ lệnh đều đọc được bằng vòng đó:
command not found là bước tìm theo PATH không ra, No such file or directory là đường dẫn tính
từ một thư mục làm việc khác, còn một exit status lạ là con số process con để lại ở bước cuối.
Từ đây, quay lại tab Shell của Bài 04, hoặc đi thẳng tới Lab.
Nguồn đọc thêm
- GNU, Bash Reference Manual, các mục Command Search and Execution, Bourne Shell Builtins và Exit Status. gnu.org/software/bash/manual
- man7.org, fork(2) và execve(2): hai bước sinh ra một process mới.
- man7.org, chdir(2): syscall đổi thư mục làm việc của process gọi nó.
- Microsoft Learn, lệnh
timecủa cmd.exe. learn.microsoft.com
Nguồn ảnh
Ảnh chụp lấy từ Wikimedia Commons, theo giấy phép ghi ở từng dòng, đã chuyển sang đen trắng, tách khỏi nền cũ và đặt vào khung vuông. Truyện tranh của turnoff.us dùng theo giấy phép phi thương mại ghi ở dòng của nó, đã chuyển sang đen trắng.
- Ảnh 1Teletype Model 33 ASR (1968), Dennis van Zuijlekom, Wikimedia Commons, CC BY-SA 2.0.
- Ảnh 1DEC VT100 terminal, Jason Scott, Wikimedia Commons, CC BY 2.0.
- Ảnh 2Inside the Linux Kernel, Daniel Stori, turnoff.us, CC BY-NC-SA 4.0.