為什麼不能在單獨的程序中更改目錄?
為什麼我們不能創建一個改變目前工作目錄的程序。就像cd命令一樣?
想想你在問什麼。你有一個 shell,你想寫一個外部程序來改變 shell 的 cwd。這甚至有意義嗎?對我來說,聽起來你並沒有完全理解 shell 只是一個接受字元串並產生一些子程序的程序(這些子程序自然繼承其父程序 shell 的 cwd)的方式。
我懷疑您可能會很笨拙地做到這一點,也許是通過使用 ptrace 將命令注入到 shell 程序中。您可以編寫自己的 shell,為“child wd”保留一個不同的設置(主要變化是在它派生一個孩子之後,它需要記住 chdir())。您可以編寫一個核心模組,讓您強制更改任意程序的 cwd,儘管我希望這會破壞事情而不是有用。
如果這種事情有任何意義,您可以(具有適當的權限)執行“ln -fs /somewhere /proc/$pid/cwd”。當我以 root 身份嘗試時,我得到“ln: cwd/: cannot overwrite directory”——來自 strace,看起來它正在檢查並註意到目標已經存在。symlink() 在這種情況下似乎會失敗,因此 symlink 命令可能必須首先刪除現有連結,我認為核心不會讓這種情況發生。
您不能更改子程序中的目前工作目錄,因為……您實際上可以做得很好。
clone(2)
用於創建程序(fork(2)
並實現像CLONE_FS
#define _GNU_SOURCE #include <sched.h> #include <unistd.h> #include <syscall.h> #include <sys/wait.h> #include <err.h> int main(void){ pid_t cpid = syscall(SYS_clone, CLONE_FS, (void*)0); if(cpid == -1) err(1, "SYS_clone"); if(cpid == 0){ /* the child, change the cwd and exit */ execl("/bin/sh", "sh", "-c", "cd /etc", (void*)0); err(1, "execl /bin/sh"); }else{ /* the parent, wait for the child, then print the cwd */ waitpid(-1, 0, __WALL); execl("/bin/sh", "sh", "-c", "pwd", (void*)0); err(1, "execl /bin/sh"); } }
這在 shell 中不起作用,因為 bourne shell(其中 bash 是一個實現)在分叉單獨的程序和執行外部命令時不使用該標誌;但是 shell 是一個過時的工作,它有很多荒謬的限制(它也沒有詞法作用域、對系統介面的直接訪問、理智和正常的語法,以及許多其他東西)。
你不應該將 45 年曆史的玩具語言的無能推斷到整個系統,它已經經歷了很多演變。
遠非自然而明顯的事情,
cd
“不可能”是一個外部命令(以及目前目錄的必要性!)這一事實更多地與 Unix 的逐步發展和 PDP-7 系統的限制有關它首先在其上執行。摘自“Dennis Ritchie——Unix 分時系統的演變”:
儘管它與目前文件系統相當相似,但 PDP-7 文件系統在一個方面顯著不同:沒有路徑名,系統的每個文件名參數都是一個簡單的名稱(沒有“/”)相對於目前目錄。
…
雖然多程序的想法確實很容易溜進來,但還是有一些沒有預料到的後遺症。在新系統出現並顯然有效後不久,其中最令人難忘的就變得明顯了。在我們歡呼雀躍的時候,發現 chdir(更改目前目錄)命令已經停止工作。對於添加 fork 可能如何破壞 chdir 呼叫,有很多程式碼閱讀和焦慮自省。終於真相大白了:在舊系統中 chdir 是一個普通的命令;它調整了附加到終端的(唯一)程序的目前目錄。在新系統下,chdir 命令正確地更改了為執行它而創建的程序的目前目錄,但該程序立即終止,對其父 shell 沒有任何影響!
來自評論:
沒有理智的 shell(執行任意外部提供的命令的程序)可以負擔使用
CLONE_FS
,因為它的所有子程序都會爭奪對目前目錄的控制權這是一個謬論。這就像“我們不能允許水管工,因為不會有更多的公車車司機”。shell 顯然只能在某些情況下這樣做。孩子們已經在標准文件描述符(使標準輸入在孩子中非阻塞也使其在父中非阻塞),在目前命名空間(在子中安裝目錄也將其安裝在父中)“戰鬥” ,以及可以通過
CLONE_*
或其他系統介面共享或拆分的所有各種部分。“工作目錄”只是執行環境的一部分,與其說是必不可少的,不如說是一種兼容性(在每個現代系統上,都可以打開另一個目錄的相對路徑——查看所有的openat(2)
,linkat(2)
等函式)。子程序不應該能夠更改其父程序的工作目錄不是自然法則,但它應該完全能夠移動或刪除它(因為它已經是這樣了)。50 年前的預設值不是一些黃金標準——例如,我寧願有一個 shell,預設值是執行
awk
或jq
過濾命令,它可以更改 shell 的工作目錄,而不是某種方式它可以清除我的文件(現在就是這種情況)。