Unix如何工作:您不敢問的一切

聞數起舞 發佈 2020-04-26T22:51:09+00:00

我不會解釋很多命令,這很無聊,網絡上已經有上百萬的教程可以做到這一點。· 這是一個強大的想法,只有在Unix設計上說:"一切都是文件",這才是正確的。

文件和進程的故事。 成為更好的軟體工程師

Unix很美。 請允許我為您畫一些快樂的小樹。 我不會解釋很多命令,這很無聊,網絡上已經有上百萬的教程可以做到這一點。

我將讓您能夠對系統進行推理。

您想做的每件奇特的事都只需一個Google搜索即可。 但是,理解為什麼解決方案可以實現您想要的功能並不相同。這就是給您真正的力量,而您不用害怕的力量。[1]

並且,因為它押韻,所以它必須是真實的。

假設您是從頭開始,我將只放足夠的命令供我們使用。 我們將探索概念,在外殼中實踐中查看它們,然後尖叫:"我明白了!"。

在此過程中,我們還將弄清楚shell到底是什麼。

但是,我們必須首先進入創造者的思想:探索Unix的哲學。

現在,我們可以假設Linux是Unix。 如果您想知道為什麼情況並非如此,則可以跳到底部再返回。 我們將一勞永逸地結束Unix與Linux的混淆。


哲學

讓我們從核心開始-Unix背後的哲學。

· 編寫可以做一件事並且做得很好的程序。

· 編寫程序以協同工作。 (沒有額外的輸出,不要堅持使用交互式輸入。)

· 編寫程序來處理文本流,因為這是一個通用接口。

Unix也接受了越差越好的哲學。

這種想法是有力的。 在更高的層次上,我們在函數式編程中看到了很多東西:構建只關注一件事的原子函數,無需額外的輸出,然後將它們組合在一起以完成複雜的事情。

組成中的所有功能都是純函數。 沒有全局變量可以跟蹤。

也許是直接的結果,Unix的設計側重於兩個主要組件:進程和文件。 Unix中的所有內容都是進程或文件。 沒有其他的。

進程和文件之間存在周期性的依存關係-如果我們開始深入解釋其中一個,我們將需要另一個來支持它。 為了打破這個循環,我們將先簡要概述每個內容,然後再深入研究。我們將重複幾次以達到最低要求。

進程

您正在運行的瀏覽器是一個進程。 如果您將其打開,那麼終端也是如此。[2] 如果沒有,現在是打開它的好時機。 如果您使用Windows,那麼Docker也可以很好地工作。 如果您使用的是Mac,請使用終端-這是Unix環境。

用更抽象的術語來說,流程是運行中的代碼實例。 作業系統將資源分配給進程(例如內存),然後將一些元數據附加到進程(例如誰是所有者),然後運行代碼。

作為資源的一部分,作業系統還為每個進程提供了三個打開文件:stdin,stdout和stderr。

文件

不是進程的所有內容都是文件。

是的,這意味著您的印表機,掃描儀,終端螢幕以及任何過程的代碼! 它們都是文件。 如果這聽起來令人困惑,請繼續閱讀。 我們將清除此問題。

文件系統中的文件就是文件—一串字節串在一起,以創建有意義的東西。 您的照片是文件。 您的目錄也是文件! 它們只包含當前目錄中存在的文件/目錄的列表,就像樹一樣。

這樣做的好處是我可以"打開"目錄文件來查看內容!

例如:

$ vim ." ==================================================================== " Netrw Directory Listing (netrw v162) " /Users/Neil/examples " Sorted by size " Quick Help: <F1>:help -:go up dir D:delete R:rename s:sort-by x:special " =================================================================== ../ ./ git-example/ unix-git/ unix-file-system-example/

我用Vim打開了一個名為..的文件,聽起來很熟悉嗎? Unix如何存儲當前目錄。 如您所見,它包含當前目錄中的文件/目錄列表。

文件只是數據流。



文件和文件系統

有了一切的想法,文件就是文件,數據就成了數據流,我們可以探索事情如何進一步發展。

在Unix系統上,用於獲取輸入和寫入輸出的流是預定義的。 這正是標準輸入stdin,標準輸出stdout和標準錯誤stderr的用途。

· stdin是輸入數據源。

· stdout是輸出數據源。

· stderr是標準錯誤輸出源。

在shell [3]中,stdin是來自鍵盤的輸入數據,而stdout和stderr都是螢幕。[4]

現在,我們可以將這些流重定向到其他地方,而我們的程序不必知道! 無論輸入來自何處(鍵盤或文本文件),對於任何正在運行的進程,輸入均來自標準輸入。

同樣,對於stdout和stderr。 當我們討論與這些流一起使用的流程時,我們將詳細討論這一點。

索引節點

要安裝文件系統,您需要一種結構來管理文件系統。 文件中不僅要處理數據,還涉及有關數據本身的信息,稱為元數據。 這包括數據的存儲位置,擁有者和查看者。

這就是inode-文件元數據的數據結構。 每個文件都有一個唯一的索引節點號。 當它存在時,它將成為文件的唯一標識符。

$ ls -li
total 0
2015005 drwxr-xr-x 6 neil X 192 23 Oct 07:36 git-example
2514988 drwxr-xr-x 4 neil X 128 9 Oct 11:37 unix-git/
2020303 drwxr-xr-x 4 neil X 128 23 Sep 11:46 unix-file-system-example/

在第一欄中看到那些數字? 這些是inode![5]

索引節點存儲所有元數據。 stat對於查看此元數據也很有用。

$ stat -LF .

drwxrwxrwx 7 A B 224 Oct 28 07:15:48 2018 ./

A和B是用戶名和組名。 Unix是一個多用戶系統。 Unix就是這樣-用戶和組是文件的屬性。

用戶屬性設置為X的文件表示X擁有該文件。 這就是使用Unix的用戶。

224是文件大小,或文件中的字節數。

2018年10月28日7:15:48是最後修改的日期。[6]

我從哪裡獲得所有這些信息? 男子ls。

現在來看看我遺漏的有趣的數字和字符:drwxrwxrwx和7。

文件權限

每個文件都具有與其關聯的權限。

還記得與文件關聯的用戶和組嗎? 每個文件都存儲誰擁有該文件以及該文件屬於哪個組。 同樣,每個用戶也都有一個用戶名和組。

進入-rwxrwxrwx字符串:這是所有者,組和其他人的權限。

· r用於讀。

· w是寫。

· x用於執行。 對於目錄,這意味著可以搜索。

您只需要三個位來代表每個用戶,組和其他用戶的權限。

您會注意到該字符串有10個字符。 第一個是特殊的輸入類型字符,用於區分目錄,符號連結,字符流(stdin)和其他一些字符。 我想知道更多。

$ stat -LF
crw--w---- 1 neil tty 16,1 Dec 12 07:45:00 2019 (stdin)

如果您想更改權限怎麼辦? 說,我不要任何人搜索我的個人文件夾(哎呀)。

Unix的創建者對此進行了思考。 有一個名為chmod的實用程序,可以修改文件的權限。 在後端,您現在知道chmod正在與文件的inode交互。

由於我們需要三個位來表示每個權限,因此我們可以將其轉換為整數並將其傳遞給chmod。

例如:chmod 721。 表示rwx-w — x表示所有者的所有權限,對該組的寫權限以及對其他人的執行權限。

我更喜歡詳細形式:

$ chmod u+rwx . # enable user for rwx
$ chmod g+w . # enable group for w
$ chmod o+x . # enable others for x

您在這裡做的完全一樣。 要為所有人設置權限,chmod a + x 更加簡單! 您也可以使用-代替+刪除權限。

為了限制對我個人文件夾的訪問,我要做:chmod og-x none-interesting-here /。

您還可以限制對自己的訪問,從而刪除自己的所有讀取,寫入和執行權限。 如果文件元數據存儲在文件本身中,則您將無法再次更改權限(因為您無法寫入文件)。

這就是inode很酷的另一個原因:它們總是可以由文件所有者和root修改,因此您可以恢復權限。 嘗試這樣做。

文件連結

有沒有想過為什麼將千兆字節文件從一個目錄移動到另一個目錄會很快,而複製相同文件卻要花一些時間呢? 你能猜出現在為什麼嗎?

這是因為當我們進行MV時,我們將移動目錄結構,而不是實際的文件數據。 索引節點是文件系統上非常有用的抽象。

我們可以採取其他行動。 我們可以將文件從一個位置連結到另一位置,或使兩個文件名指向同一文件。

指向同一文件的兩個文件名是硬連結。 將它們視為文件的別名。 您已經看到了兩個硬連結:。 和..是到系統中當前目錄和父目錄的硬連結。

從一個地方到另一個地方的連結是符號連結。 符號連結是與原始文件分開的新文件,該文件連結到原始文件。

當您要修復需要在新環境中運行的腳本或製作文件副本,以滿足希望該文件位於另一個位置的新程序的安裝要求時,這些選項很有用。

$ ls -li
total 0
25280489 -rw-r--r--  1 neil  X  0  8 Dec 08:48 a
$ man ln # to check syntax for hard links
$ ln a x # create x as a hard link to a
$ ls -li
total 0
25280489 -rw-r--r--  2 neil  X  0  8 Dec 08:48 a
25280489 -rw-r--r--  2 neil  X  0  8 Dec 08:48 x
# Notice both files have the same inode number.
# Modifying x or a is the same thing - both files get modified together.
$ ln -s a y # create symbolic link to a
$ ls -li
total 0
25280489 -rw-r--r--  2 neil  X  0  8 Dec 08:48 a
25280489 -rw-r--r--  2 neil  X  0  8 Dec 08:48 x
25280699 lrwxr-xr-x  1 neil  X  1  8 Dec 08:54 y -> a
# y is a symbolic link, a new small file - see size is 1. 
$ cat y # shows that y (a) is empty
$ echo lsd >> y
$ cat y
lsd
$ cat a # modifying y modifies a
lsd
$ cat x # a is x
lsd

我已經在評論中解釋了上面的情況。

現在,如果刪除y指向的文件,該怎麼辦?

$ rm a$ cat y
cat: y: No such file or directory# y becomes useless$ ls -li
25280489 -rw-r--r--  1 neil  X  12  8 Dec 08:56 x
25280699 lrwxr-xr-x  1 neil  X   1  8 Dec 08:54 y -> a

這是一個懸掛的符號連結。 沒用的。

讀寫許可權後的數字,即執行stat -LF時的7。 是文件的硬連結數。

當我創建x時,數字增加到兩個。 當我刪除a時,數字回落到1。

我們也可以確認。 和..確實是硬連結。 你能怎麼想?

$ ls -ail
25280488 drwxr-xr-x   7 neil  X   224  9 Dec 20:19 .
 1289985 drwxr-xr-x+ 83 neil  X  2656 10 Dec 08:13 ..
25390377 drwxr-xr-x   5 neil  X   160  9 Dec 19:13 sample_dir
$ cd sample_dir
$ ls -ail
25390377 drwxr-xr-x  5 neil  X  160  9 Dec 19:13 .
25280488 drwxr-xr-x  7 neil  X  224  9 Dec 20:19 ..
25390378 -rw-r--r--  1 neil  X    0  9 Dec 19:13 a 】『

檢查索引節點號。 ..在sample_dir中是25280488,與..相同 在父目錄中。 另外,父目錄中的sample_dir是25390377,與相同。 在sample_dir中。

文件結構

它可以幫助我想像文件系統像樹形數據結構(實際上就是這樣)。 每個節點(索引節點)都有一個指向其父節點,自身及其所有子節點的指針。 這形成目錄結構。

/根目錄的父目錄是什麼?

您已有足夠的知識來回答這個問題。 我做的第一件事是vim /看看是否有一個父指針。 是的 然後,我做了ls -ail來查看父級的索引節點。 它指向。,即/。

綜上所述,

· 文件系統是使用inode和目錄文件構建的。

· 用戶是文件和進程的屬性。 此信息存儲在inode中。

· 索引節點在文件系統中是唯一的。

· 可以掛載多個文件系統並將其抽象為一個邏輯樹。



進程

首先,讓我們擺脫這些定義。 關於進程,要記住三個組件:

· 程序文件:代碼和數據。

· 過程映像:這將存儲堆棧,當前定義的變量,數據,地址空間等。 在運行時,作業系統完全知道如何使用此映像重新創建過程。

· 進程:內存中正在運行的程序。

進程開始運行時,它將從父進程繼承用戶ID和組ID。 此信息控制對該進程的訪問級別。

注意:訪問控制對於安全系統至關重要。 這就是為什麼在生產環境中運行裸Docker容器可能會出現這樣的問題的原因之一:它需要以root身份運行,這意味著可能會發生不好的事情。

我們可以使用setuid或setgid來使進程繼承文件所有者權限。 setuid允許進程繼承相關文件的用戶ID。

例如,要在Linux上更改密碼(請參閱Mac的此連結)-我們需要修改文件/ etc / passwd。 但是,在檢查權限時,我們看到只有root有權訪問此文件。[7]

$ ls -ail /etc/passwd
3541354 -rw-r--r-- 1 root root 681 Nov 28 08:47 /etc/passwd

因此,當我們調用幫助更改密碼的實用程序/ usr / bin / passwd時,它將繼承我們的用戶ID,該用戶ID將導致對/ etc / passwd的訪問被拒絕。 這是setuid有用的地方-它允許我們以root身份啟動usr / bin / passwd。

$ ls -al /usr/bin/passwd
-rwsr-xr-x 1 root root 27936 Mar 22 2019 /usr/bin/passwd

執行權限中的s而不是x表示此過程將以root用戶身份運行。

要設置和刪除該位,我們可以再次使用chmod。

$ chmod u-s /usr/bin/passwd
$ ls -al /usr/bin/passwd
-rwxr-xr-x 1 root root 27936 Mar 22 2019 /usr/bin/passwd

我是在Docker上完成的,因此我真正的文件系統是安全的。

屬性

就像文件系統上的所有文件都具有唯一的索引節點一樣,進程也具有稱為進程ID或pid的唯一標識符。

就像所有文件都具有指向其父目錄的連結一樣,每個進程都具有指向生成它的父進程的連結。

就像文件系統根的存在方式(/)一樣,有一個特殊的根父進程稱為init。 它通常具有pid 1。

與父目錄本身為(/)的文件系統的根目錄不同,init的ppid為0,這通常意味著它沒有父目錄。 pid 0對應於內核調度程序,它不是用戶進程。

生命周期

在Unix中,有一個常見的模式說明進程如何工作。

通過克隆現有的父進程(fork()),可以創建一個新的子進程。 這個新的子進程調用(exec())將子進程中運行的父進程替換為該子進程想要運行的進程。

接下來,子進程調用exit()終止自身。 它只會傳遞退出代碼。 0表示成功,其他所有都是錯誤代碼。

父進程需要調用wait()系統調用才能訪問此退出代碼。 對於產生的每個過程都重複此循環。

這裡有些事情可能會出錯。

如果父母不致電wait()怎麼辦? 這會導致殭屍進程,這是資源泄漏,因為作業系統無法在父進程消耗退出代碼之前清理進程。

如果父母在子進程之前去世怎麼辦? 這將導致一個孤立的過程(我保證我不會對此進行彌補)。 初始化進程(特殊的根父進程)採用了孤立進程,然後等待子進程完成。

在計算機世界的自然秩序中,孩子死於父母之前。

父母如何從孩子那裡獲得更多信息? 它不能通過退出代碼,因為這是進程唯一可以返回到父級的東西。

流程與常規函數不同,在常規函數中,您只需將響應返回給調用函數即可。 但是,還有其他方法可以進行進程間通信

我們將通過一個示例來詳細介紹事物的工作方式。 在此之前,我們需要更多信息。

文件重定向

還記得作業系統如何為每個正在運行的進程提供三個打開的文件嗎? 我們有權將這些文件重定向到我們想要的任何文件。

重定向stdout,2>重定向stderr,和<重定向stdin。

例如,someBinary 2>&1將stderr重定向到stdout。

0、1和2分別是stdin,stdout和stderr文件的簡寫。

注意:./someBinary 2> 1不會像您期望的那樣工作,因為語法是file-descriptor> file。 2> 1表示stderr將被重定向到名為1的文件。&運算符提供文件中的文件描述符。

文件重定向發生在命令運行之前。 作業系統打開新文件(通過>)時,它已經刪除了這些文件中的所有內容。

因此,無法對res.txt> res.txt進行排序。

$ cat res.txt # check contents of res
d
c
b
a
$ sort res.txt # sort res
a
b
c
d
$ sort res.txt > res.txt
$ cat res
# empty

提示:通過在外殼程序中設置noclobber選項,可以確保沒有任何重定向破壞現有文件。

$ set -o noclobber
$ sort res.txt > res.txt
-bash: res.txt: cannot overwrite existing file

但是,它將與>>一起使用,因為在這種情況下,您將附加到文件中。

$ sort res.txt >> res.txt
$ cat res.txt
d
c
b
a
a
b
c
d

閱讀有關重定向的更多信息。



Unix中的層

我們可以將Unix視為洋蔥。 核心是硬體-主板,CPU和許多我不太了解的電晶體。 一層是內核。

內核

內核是負責與文件系統和設備進行交互的核心。 它還處理流程調度,任務執行,內存管理和訪問控制。

內核公開了API調用,以構建任何可以利用的東西。 最受歡迎的是exec(),fork()和wait()。

Unix實用程序

另一層是Unix實用程序。 這些是超級有用的進程,可以幫助我們與內核進行交互。 他們通過內核提供的exec()和fork()之類的系統調用來執行此操作。

您可能已經聽說過許多實用程序。 您可能使用了最著名的一種:Shell。

其他包括:python,gcc,vi,sh,ls,cp,mv,cat,awk。

您可以從shell調用其中的大多數。 bash,zsh,ksh只是shell的不同變體。 他們做同樣的事情。

人們發現令人望而生畏的另一個實用工具是文本編輯器Vim。 Vim值得擁有自己的職位,這就是我在這裡創建的

有趣的事實:Shell之所以稱為Shell,是因為它是內核之外最近的一層。 它在保護性…外殼中覆蓋了內核。



Shell如何工作

還記得shell是一個過程嗎? 這意味著作業系統啟動時會提供三個文件供其使用:stdin,stdout和stderr。

從終端運行時,stdin連接到鍵盤輸入。 您編寫的內容將傳遞到終端中。 這是通過稱為電話打字機或tty的文件發生的。

stdout和stderr也連接到tty,這就是為什麼您運行的任何命令的輸出和錯誤都顯示在終端上的原因。

您打開的每個終端都會通過tty分配一個新文件,以使來自一個終端的命令不會破壞另一個終端。 您可以通過tty命令找到終端連接到的文件。

$ tty
/dev/ttys001 # on linux, this looks like: /dev/pts/0

現在,您可以做一些時髦的事情:由於Shell從該文件讀取,因此您也可以讓另一個Shell寫入該文件,或者將Shell一起破壞。 我們試試吧。 (還記得如何從上面的過程部分重定向文件嗎?)

打開第二個終端。 輸入:

$ echo "Echhi" > /dev/ttys001 # replace /dev/ttys001 with your tty output

注意在第一個終端中發生的事情。

嘗試回顯ls,這次是列出文件的命令。 為什麼第一個終端不運行命令?

它不會運行該命令,因為寫入終端的流是第二終端的標準輸出,而不是第一終端的stdin流。

請記住,只有通過stdin輸入的輸入才作為輸入傳遞到shell。 其他所有內容僅顯示在螢幕上。 在這種情況下,即使碰巧是同一文件,也無需擔心該過程。

上面內容的自然擴展是,當您重定向標準輸入時,命令應該運行。 聽起來很合理,請嘗試一下。

警告:執行此操作的一種方法是bash </ dev / ttys001。 這不太好用,因為現在有兩個進程希望從該文件輸入信息。

這是一個未定義的狀態,但是在我的Mac上,一個字符轉到一個終端,另一個字符轉到第二個終端,然後繼續。 這很有趣,因為要退出新外殼,我必須鍵入eexxiitt。 然後我丟了兩個炮彈。

$ echo ls > ls.txt # write "ls" to a file
$ cat ls.txt # check what's in file
ls$ bash < ls.txt 
Applications
Music
Documents
Downloads

有一種更好的方法可以做到這一點,我們將在稍後介紹。

這裡有些微妙的事情。 這個新的bash流程(我們從現有的bash流程開始)如何知道在哪裡輸出內容?

我們從不指定輸出流,僅指定輸入流。 發生這種情況是因為進程從其父進程繼承。

每次在終端上編寫命令時,shell都會創建一個重複進程(通過fork())。

從man2叉:

子進程具有其父描述符的副本。 這些描述符引用相同的基礎對象,例如,子對象和父對象之間共享文件對象中的文件指針,以便子進程中的描述符上的lseek(2)可以影響後續的讀寫操作。 父母。

Shell還使用此描述符複製為新創建的進程建立標準輸入和輸出,以及建立管道。

分叉後,這個新的子進程將從父級繼承文件描述符,然後調用exec(execve())執行命令。 這將替換過程映像。

來自man 3 exec:

本手冊頁中描述的功能是功能execve(2)的前端。

來自man 2 execve [8]:

在調用過程映像中打開的文件描述符在新過程映像中保持打開狀態,但已設置close-on-exec標誌的文件描述符除外

因此,我們的文件描述符與原始bash進程相同,除非我們通過重定向對其進行更改。

在執行此子進程時,父進程等待子進程完成。 發生這種情況時,控制權將返回給父進程。

請記住,子進程不是bash,而是代替bash的進程。 使用ls,該過程將文件列表輸出到stdout後立即返回。

注意:並非Shell上的所有命令都會產生fork和exec。 那些不被稱為內置命令的命令。 有些是出於必要內置的。 由於子進程無法將信息傳遞迴父母,因此其他進程可以使事情更快。

例如,設置環境變量在子Shell中不起作用,它無法將值傳遞迴父Shell。 您可以在此處找到列表。

這是我喜歡的示範。

您是否曾經想過,當某事正在運行並將東西輸出到終端時,您可以編寫下一條命令並在現有過程完成後立即使它們起作用,這有多奇怪?

$ sleep 10;
ls
cat b.txt
brrr
# I stop typing here
$ ls
b       c       y$ cat b.txt
defbjehb$ brrr
-bash: brrr: command not found

只是被阻止的進程,輸入流仍在接受數據。 由於我們要讀取/寫入的文件是相同的(tty),因此我們可以看到鍵入的內容以及睡眠的時間10; 返回時,shell為ls創建另一個進程,再次等待,然後對cat b.txt相同,然後再次對brrr。

我用了10點睡眠; 進行演示,因為其他命令發生得太快,我無法在控制項返回到父bash進程之前鍵入任何內容。

現在是嘗試exec內置命令的好時機(它將替換當前進程,因此它將終止您的shell會話)。

exec echo Bye

echo也是內置命令。

如果您想用C自己實現Shell,我建議您使用以下資源。



管道

掌握了外殼如何工作的知識後,我們便可以冒險進入管道的世界:|。

它把兩個過程聯繫在一起,並且它的工作方式很有趣。

還記得我們開始時的哲學嗎? 做一件事,並做好。 現在,我們所有的公用程序都可以正常工作,我們如何使它們一起工作?

這是管道|進入管道的地方。它表示對pipe()的系統調用,它所做的只是重定向進程的stdin和stdout。

由於事情的設計是如此出色,因此原本就很複雜的功能可以簡化為這一點。 每當您使用管道或終端上的任何東西時,只要想像一下輸入和輸出文件的設置方式,就不會有問題。[9]

讓我們從一種更好的方法將輸入直接定向到bash開始,而不是像以前那樣使用臨時文件(ls.txt)。

$ echo ls | bash
Applications
Music
Documents
Downloads

該圖有點簡化了管道的重定向。 您知道外殼現在是如何工作的,因此您知道頂部bash分叉了另一個與tty連接的bash,後者生成了ls的輸出。

您還知道頂部bash是從底部bash派生的,這就是為什麼它繼承了底部bash的文件描述符的原因。 您還知道較低的bash並沒有進行新的處理,因為echo是內置命令。

讓我們以一個更複雜的示例結束本節:

$ ls -ail | sort -nr -k 6 | head -n 1 | cut -f 9 -d ' '2656

該管道找出當前目錄中最大的文件並輸出其大小。

這樣做可能是一種更優雅的方法,僅需一次Google搜索即可,但這很好地舉例說明了。 誰知道這已經內置到ls中了?

注意,stderr是如何始終直接路由到tty的嗎? 如果要重定向stderr而不是stdout到管道怎麼辦? 您可以在管道之前切換流。

$ error-prone-command 2>&1 >/dev/null



關於PATH的一切

局部變量是可以在shell中創建的變量。 它們位於外殼的本地,因此不會傳遞給子級。 (請記住,每個非內置命令都在沒有這些局部變量的新外殼中。)

環境變量(環境變量)類似於全局變量。 他們被傳遞給孩子。 但是,子進程中對環境變量的更改無法傳遞給父進程。 請記住,除了退出代碼,孩子與父母之間沒有任何聯繫。

試試這個:從bash調用bash。 第一個bash正在等待第二個bash退出,而第二個bash正在等待第三個bash。

當您調用exec時,退出會自動發生。 如果不是,則您要自己鍵入exit,以將退出代碼發送給父級。 退出兩次,即可恢復原始狀態。

現在,進行思想實驗:當將ls輸入到shell中時會發生什麼? 您知道與tty一起發生的fork(),exec()和wait()循環。

但是,即使在這種情況發生之前,ls只是另一個實用程序功能,對嗎? 這意味著某個地方有一個程序文件,其中包含執行fork()和其他所有功能的C代碼。

該二進位文件不在當前目錄中(您可以使用ls -a檢查)。 對我來說,如果這些文件在我的當前目錄中,我可以通過在外殼中鍵入它們的名稱來執行它們。 它們是可執行文件。

ls程序文件在哪裡?

還記得文件系統樹是如何分層的嗎? 這是瘋狂的命令。

所有基本級別目錄都具有特定功能。 例如,所有Unix實用程序和一些其他程序都進入/ bin目錄。 Bin代表二進位文件。 如果您想了解更多信息,則有一百萬本教程。

這對我們來說已經足夠了。 我住在/ bin。 因此,您可以這樣做:

$ /bin/ls

a b c

與運行ls相同。

但是,shell如何知道要在bin中查找ls?

這就是神奇的環境變量PATH出現的地方。讓我們首先來看它。

$ echo

$PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

並且,要查看所有環境變量,我們可以執行以下操作:

$ env
HOSTNAME=12345XXXX
TERM=xterm
TMPDIR=/tmp
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/test
LANG=en_US.UTF-8
SHLVL=1
HOME=/root
LANGUAGE=en_US:en
LESSOPEN=||/usr/bin/lesspipe.sh %s
container=oci
_=/bin/env

PATH是用冒號分隔的目錄列表。 當shell看到沒有絕對路徑的命令時,它將在$ PATH環境變量中查找,依次進入每個目錄,並嘗試在其中查找文件。 它執行找到的第一個文件。

注意/ bin在PATH中的方式,這就是ls起作用的原因。

如果我從PATH中刪除所有內容,會發生什麼? 沒有絕對的路徑,任何事情都不應該工作。

$ PATH=''

ls-bash: ls: No such file or directory

上面的語法僅用於設置一個命令的環境變量。 舊的PATH值仍然存在於外殼中。

$ echo $PATH

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

注意:由於echo是內置的shell,因此無法執行PATH ="''echo $ PATH。 但是,如果您使用PATH =''啟動了一個新的shell進程,然後執行了echo操作,那麼它將起作用。

PATH)

()是新子shell的語法。 我知道,我首先不解釋很多信息,但這全是語法級別,僅需一次Google搜索。 從正面來看,它可以確保該博客文章不會變成一本書。

您是否聽說過./是運行所創建文件的方式? 您為什麼不能像bash一樣運行它們? 現在你知道了。 當您執行./時,這就是您要執行的文件的確切路徑。 bash有效,因為它位於PATH上。

因此,有意義的是,如果當前目錄始終位於PATH上,則腳本將按名稱運行。

讓我們嘗試一下。

$ vim script.sh
# echo "I can work from everywhere?"
$ chmod a+x script.sh 
$ ls
script.sh
$ script.sh
-bash: script.sh: command not found # not on PATH
$ ./script.sh # path to file is defined
I can work from everywhere?

現在,將其添加到當前PATH中。 然後只運行script.sh。

$ PATH=${PATH}:. script.sh # this appends . to PATH, only for this command
I can work from everywhere?
$ export PATH=${PATH}:. # this sets the PATH variable permanently to include .
$ script.sh # calling script.sh without the ./
I can work from everywhere?
$ cd .. # go one directory up
$ script.sh # this shows that PATH directories aren't searched recursively
-bash: script.sh: command not found # so script doesn't run anymore

警告:不正確的做法是將當前目錄包含在PATH中。 有幾個問題。 您永遠不能確保任何命令的執行都按預期進行。

如果您有一個名為ls的二進位文件,它是當前目錄中的一種病毒(可從Internet下載),但您打算執行/ bin / ls怎麼辦?

閱讀更多

下次當您看到"沒有這樣的文件或目錄"錯誤時,當您知道該文件存在(也許您剛剛安裝了該文件)時,便知道問題出在哪裡。 路徑被破壞!

它安裝在PATH以外的位置,因此只能從安裝它的位置進行調用。 要解決此問題,您現在知道可以將該目錄添加到PATH,也可以通過其絕對路徑調用該文件。

有趣的事實:Python在搜索導入時具有類似的結構,該結構使用PYTHONPATH env變量。



編寫Shell腳本

這篇文章的長度已經超出了我的預期。 此外,使用Shell進行編程已經在網上廣泛討論了。 但是為了完整起見,這裡是手冊的連結和不錯的教程。



包管理

假設您編寫了一個新工具。 它在您的計算機上確實運行良好,現在您想將其出售給其他用戶。 等等,我的意思是,本著開源的精神,您想將其提供給他人使用。

您還希望節省他們的PATH麻煩。 更妙的是,您希望將文件安裝在正確的位置:二進位文件進入/ usr / bin /(它已經在PATH中),而依賴項則位於主二進位文件可以找到它的地方。

包管理器正好解決了這個問題。 他們沒有讓您頭疼,而是使事情正常進行。

我知道三個主要的軟體包管理器:dpkg,rpm和自製軟體。 它們每個都在不同的Linux發行版上工作(如果您不確定這是什麼意思,請參見下一節)。

但是,就像分發的數量一樣,它們中有數百種在野外。

dpkg是Debian軟體包管理器,但是您可能已經聽說過在它之上構建的用於管理軟體包的非常有用的工具:apt。

每次使用apt install來安裝新軟體包時,您都在利用此軟體包管理器的功能,以確保一切都在需要的地方進行。

在開發方面,這意味著確保要創建的工具與程序包管理器兼容。 例如,以下是在C和Python中執行此操作的方法。

rpm是Red Hat軟體包管理器,它在頂部還有一個有用的工具:yum,它也處理依賴項。

homebrew是macOS上的軟體包管理器,每次醞釀安裝某些軟體時都在使用它。

它們使生活變得輕鬆。

它們是如此方便,以至於程式語言也有自己的軟體包管理器! 例如,pip是流行的Python工具安裝程序。 有用於Ruby的捆綁器,用於Swift / iOS的可可和其他幾種捆綁器。



Unix的簡要歷史

Unix是第一個允許多個用戶使用它的作業系統,每個用戶可以同時運行多個程序。

現在這聽起來似乎很瑣碎,因為幾乎每個作業系統都具有此功能,但是它首次問世時是革命性的。 在大型主機上租賃時間的日子已經過去。 您可以讓程序在後台運行,而其他人來做。

顧名思義,Unix是一個多用戶多任務作業系統。

這是專有的,AT&T是唯一可以出售它的公司。 (貝爾實驗室在1970年代開發了它)。 他們選擇了許可模型,並很快提出了一個規範,稱為"單一UNIX規範"。

這些是一組準則,遵循它們的任何系統都可以被認證為Unix系統。

大約在同一時間,一些人對Unix的專有性不滿意,並提出了另一個名為Linux的開源作業系統內核。

這些系統受到Unix哲學的啟發並實現了可移植性,因此遵循POSIX標準,該標準是Unix的一個子集。 這些系統因此也被稱為類Unix。[10]

事情變得有些混亂。 Linux是基於Linux內核的一系列作業系統。 沒有稱為Linux的單一作業系統。

相反,我們所擁有的是Debian,Ubuntu,Fedora,CentOS,Red Hat,Gentoo等。這些是Linux內核的發行版(通常稱為發行版)。 完善的作業系統。

有什麼不同? 有些是為特定目的而構建的(例如:Kali Linux附帶了安全測試工具)。

包管理,包更新頻率和安全性方面的大多數差異。

如果您想了解更多,請訪問opensource.com。

有趣的事實:Mac OS X經過Unix認證。



結論

我們已經介紹了很多。 讓我們花一點時間將它們放在一起。

Unix是成熟的作業系統,Linux是受Unix啟發的內核(作業系統的核心)。 他們專注於做一件事情,並做好。

一切都是過程或文件。 內核是核心,它公開了系統調用,這些實用程序可以利用它們。 進程使用文件作為輸入和輸出。 我們可以控制這些文件,可以重定向它們,這對過程沒有影響。

管道可以將輸出從一個過程重定向到另一個過程的輸入中。 Shell中的每個命令首先派生,然後執行,然後將退出代碼返回給等待的父對象。

還有很多。 如果有可能進行無損壓縮,那麼我會在後期進行。

因此,歡迎來到野外,您很好。

出口

感謝Vatika Harlalka,Nishit Asnani,Hemanth K. Veeranki和Hung Hoang閱讀了此草稿。




腳註

· 這只是一首詩嗎?

· 終端也像外殼一樣運行著子進程。 您可以通過ps -ef查看所有正在運行的進程。

· Shell是用於與作業系統交互的接口。 它既可以是命令行介面(CLI),也可以是圖形用戶介面(GUI)。 在本文中,我們僅關注CLI。 打開終端時,歡迎您的默認程序是外殼程序。 閱讀更多

· 這不是100%正確的,還有更多細微差別,我們將儘快解決。

· 我花了太多時間在iPhone和iOS上。 它是一個索引節點,而不是一個iNode。

· 另外,這是我開始編寫本指南的時間。 大概我完成了。 注意年份。

· 我從man 5 passwd獲得了所有這些信息。

· 本手冊分為八個部分,每個部分用於特定目的。 閱讀更多。

· 這是一個強大的想法,只有在Unix設計上說:"一切都是文件",這才是正確的。

· 有趣的事實-由於它們未經認證,因此不能稱為Unix,並且Unix是商標。


(本文翻譯自Neil Kakkar的文章《How Unix Works: Everything You Were Too Afraid to Ask》,參考:https://medium.com/better-programming/how-unix-works-everything-you-were-too-afraid-to-ask-f8396aeb2763)

關鍵字: