標籤:

Bash的「|」到底有什麼用,cd..|pwd為什麼還是當前路徑?

"|"不是把前面的output變成後面的input嗎,那既然cd..是去上一層路徑,為什麼cd..|pwd反而還是在當前的路徑呢


管道前後的東西是同時(讀作:前後腳)執行的,但這個地方的問題跟「執行順序」沒有關係。很容易發現,就算是

cd .. | anything

pwd

得到的輸出仍然是執行之前的當前路徑。

man bash 搜索 Pipelines 一節最後一句:

Each command in a pipeline is executed as a separate process (i.e., in a subshell).

即:就算 cd .. 這樣的「內部命令」,都是 bash -c cd .. 這樣通過 subshell 執行的。所以執行管道的 shell 本身不受影響。


管道可以把前一個命令的輸出當成後一個命令的輸入, 這個就可以把linux下的各種命令按自己的需要組合起來, 想想是不是有點像函數的中的鏈式調用?

不過管道前後的兩個命令不是在同一個一個進程中執行, 而是分別在當前shell進程的fork出來的不同子進程中執行的, 而兩個子進程繼承的當前路徑都是父進程的當前路徑。


管道不是順序執行的


cd沒有output,pwd也不讀input


以下是關於「為什麼 cd .. | pwd 執行後依然在當前路徑」的回答。

首先一點,在 一個 shell 中執行的程序都一般會被啟動為以此 shell 為父進程的子進程。當把幾個命令用管道連起來時,它們會被並行執行,並且其標準輸入輸出(stdin/stdout)會被連起來。

譬如說,當我執行「ls | sort | head -n 10」的時候,進程樹是這個樣子的:

├─bash─┬─ls
│ ├─sort
│ └─head -n 10

事實上在 unix 下所有進程都有且只能有一個當前工作目錄(current working directory),可以用 getcwd() 函數來獲取。現在問題是,如果 shell 要修改自己的當前目錄,應該怎麼做?很明顯上面的方法開子進程是不行的,因為那樣即使子進程的當前目錄改了,也無法干涉到父進程(即 shell 進程)的當前目錄。所以一般來說採用的方法是在 shell 內部實現解析 cd 命令來切換 shell 自己的當前目錄。這點可以如下方式驗證:

~ $ which ls
/bin/ls
~ $ which cd
which: no cd in (/bin:/usr/bin:/usr/local/bin)

可見 cd 並非一個可執行的程序。

令一方面,我們可以測試 cd 的效果。這裡用到 shell 語法「(&)」,可以強行令 & 命令在子進程中執行。常規作法:

~ $ cd /
/ $

切換成功,試試子進程的方法:

~ $ (cd /)
~ $

不太有效。注意 cd 是個 shell 指令而非可執行程序,所以一般 shell 會開一個子 shell 來執行之。

那麼當執行「cd .. | pwd」到底發生了甚麼?

首先兩個子進程會被 shell 創建出來,一個是子 shell,一個是 pwd 程序,剛 fork 出來的子進程的當前目錄和父進程一致。這時的進程樹如下:

├─bash (cwd: ~) ─┬─bash (cwd: ~)
│ └─pwd (cwd: ~)

然後子 shell 的 cd 指令執行,其當前目錄變為 /home/xxx(假設「~」為「/home/xxx」)。

├─bash (cwd: ~) ─┬─bash (cwd: /home)
│ └─pwd (cwd: ~)

子 shell 執行完 cd 就結束了。這並不會影響到父 shell 和 pwd 進城的當前目錄。pwd 的作用就是輸出自己的當前目錄,所以它把 /home/xxx 列印出來然後也結束掉了。這時 stdout 上只有 /home/xxx 的輸出而已。

題主的理解有些偏差,如 @唐生 所言,cd 的沒有輸入也沒有輸出,pwd 只會輸出且不會讀取任何輸入。所以把這兩個命令用管道連起來沒有實質的意義。


推薦閱讀:

在Bash中按Ctrl-S為什麼會失去響應?
20.6 Shell 循環表達式 (從新手到菜鳥的Linux教程)
如何評價 Windows 版「bash」(及其相關 *nix 子系統)?
什麼是 .bashrc,為什麼要編輯 .bashrc? | Linux 中國
linux刪除根目錄後發生了什麼?

TAG:Linux | Bash |