Patrice đã xác định nguồn gốc của vấn đề trong câu trả lời của anh ta , nhưng nếu bạn muốn biết làm thế nào để giải quyết vấn đề đó, thì đây là câu chuyện dài.
Thư mục làm việc hiện tại của một quá trình không có gì bạn nghĩ quá phức tạp. Đây là một thuộc tính của quy trình xử lý một tệp có thư mục loại trong đó các đường dẫn tương đối (trong các cuộc gọi hệ thống được thực hiện bởi quy trình) bắt đầu từ đó. Khi giải quyết một đường dẫn tương đối, kernel không cần biết (a) đường dẫn đầy đủ đến thư mục hiện tại, nó chỉ đọc các mục nhập thư mục trong tệp thư mục đó để tìm thành phần đầu tiên của đường dẫn tương đối (và ..
giống như bất kỳ đường dẫn nào khác tập tin về vấn đề đó) và tiếp tục từ đó.
Bây giờ, là một người dùng, đôi khi bạn muốn biết thư mục đó nằm ở đâu trong cây thư mục. Với hầu hết các Unices, cây thư mục là một cây, không có vòng lặp. Đó là, chỉ có một đường dẫn từ gốc của cây ( /
) đến bất kỳ tệp nào. Con đường đó thường được gọi là con đường kinh điển.
Để có được đường dẫn của thư mục làm việc hiện tại, một quá trình phải làm là chỉ cần đi lên ( xuống nếu bạn muốn thấy một cây có gốc ở phía dưới) cây trở lại gốc, tìm tên của các nút trên đường.
Ví dụ, một quá trình cố gắng tìm ra thư mục hiện tại của nó /a/b/c
, sẽ mở ..
thư mục (đường dẫn tương đối, ..
mục nhập trong thư mục hiện tại) và tìm một tệp có thư mục loại có cùng số inode như .
, tìm ra rằng c
phù hợp, sau đó mở ../..
và như vậy cho đến khi nó tìm thấy /
. Không có sự mơ hồ ở đó.
Đó là những gì các hàm getwd()
hoặc getcwd()
C làm hoặc ít nhất được sử dụng để làm.
Trên một số hệ thống như Linux hiện đại, có một lệnh gọi hệ thống để trả về đường dẫn chính tắc cho thư mục hiện tại tìm kiếm trong không gian kernel (và cho phép bạn tìm thư mục hiện tại ngay cả khi bạn không đọc quyền truy cập vào tất cả các thành phần của nó) , và đó là những gì getcwd()
gọi đó. Trên Linux hiện đại, bạn cũng có thể tìm đường dẫn đến thư mục hiện tại thông qua readlink () trên /proc/self/cwd
.
Đó là những gì hầu hết các ngôn ngữ và shell đầu tiên làm khi trả lại đường dẫn đến thư mục hiện tại.
Trong trường hợp của bạn, bạn có thể gọi cd a
như thể lần như bạn muốn, bởi vì đó là một liên kết tượng trưng đến .
, thư mục hiện hành không thay đổi vì vậy tất cả các getcwd()
, pwd -P
, python -c 'import os; print os.getcwd()'
, perl -MPOSIX -le 'print getcwd'
sẽ quay trở lại của bạn ${HOME}
.
Bây giờ, symlink đã làm phức tạp tất cả điều đó.
symlinks
cho phép nhảy trong cây thư mục. Trong /a/b/c
, nếu /a
hay /a/b
hoặc /a/b/c
là một liên kết tượng trưng, sau đó con đường kinh điển của /a/b/c
sẽ là một cái gì đó hoàn toàn khác nhau. Đặc biệt, các ..
mục trong /a/b/c
không nhất thiết phải /a/b
.
Trong vỏ Bourne, nếu bạn làm:
cd /a/b/c
cd ..
Hoặc thậm chí:
cd /a/b/c/..
Không có gì đảm bảo bạn sẽ kết thúc /a/b
.
Giống như:
vi /a/b/c/../d
không nhất thiết phải giống như:
vi /a/b/d
ksh
đã giới thiệu một khái niệm về một thư mục làm việc hiện tại hợp lý để bằng cách nào đó làm việc xung quanh đó. Mọi người đã quen với nó và POSIX cuối cùng đã xác định rằng hành vi đó có nghĩa là hầu hết các shell hiện nay cũng làm điều đó:
Đối với các lệnh cd
và pwd
lệnh dựng sẵn ( và chỉ cho chúng (mặc dù cũng cho popd
/ pushd
trên các vỏ có chúng)), shell duy trì ý tưởng riêng của nó về thư mục làm việc hiện tại. Nó được lưu trữ trong $PWD
biến đặc biệt.
Khi bạn làm:
cd c/d
ngay cả khi c
hoặc c/d
là liên kết tượng trưng, trong khi $PWD
containes /a/b
, nó gắn thêm c/d
đến cùng để $PWD
trở thành /a/b/c/d
. Và khi bạn làm:
cd ../e
Thay vì làm chdir("../e")
, nó làm chdir("/a/b/c/e")
.
Và pwd
lệnh chỉ trả về nội dung của $PWD
biến.
Điều đó hữu ích trong các shell tương tác vì pwd
xuất ra một đường dẫn đến thư mục hiện tại cung cấp thông tin về cách bạn đến đó và miễn là bạn chỉ sử dụng ..
trong các đối số cd
và không phải các lệnh khác, điều đó sẽ ít làm bạn ngạc nhiên hơn, bởi vì cd a; cd ..
hoặc cd a/..
nói chung sẽ khiến bạn quay lại đến nơi bạn đã ở.
Bây giờ, $PWD
không được sửa đổi trừ khi bạn làm a cd
. Cho đến lần tiếp theo bạn gọi cd
hoặc pwd
, rất nhiều điều có thể xảy ra, bất kỳ thành phần nào của $PWD
cũng có thể được đổi tên. Thư mục hiện tại không bao giờ thay đổi (luôn luôn là cùng một nút, mặc dù nó có thể bị xóa), nhưng đường dẫn của nó trong cây thư mục có thể thay đổi hoàn toàn. getcwd()
tính toán thư mục hiện tại mỗi lần nó được gọi bằng cách đi xuống cây thư mục để thông tin của nó luôn chính xác, nhưng đối với thư mục logic được triển khai bởi shell POSIX, thông tin trong đó $PWD
có thể trở nên cũ. Vì vậy, khi chạy cd
hoặc pwd
, một số đạn pháo có thể muốn bảo vệ chống lại điều đó.
Trong trường hợp cụ thể đó, bạn thấy các hành vi khác nhau với các vỏ khác nhau.
Một số người thích ksh93
bỏ qua vấn đề hoàn toàn, do đó sẽ trả lại thông tin không chính xác ngay cả sau khi bạn gọi cd
(và bạn sẽ không thấy hành vi mà bạn đang thấy bash
ở đó).
Một số thích bash
hoặc zsh
kiểm tra $PWD
vẫn là một đường dẫn đến thư mục hiện tại cd
, nhưng không phải trên pwd
.
pdksh không kiểm tra cả hai pwd
và cd
(nhưng khi pwd
, không cập nhật $PWD
)
ash
(ít nhất là cái được tìm thấy trên Debian) không kiểm tra và khi bạn thực hiện cd a
, nó thực sự làm cd "$PWD/a"
như vậy, vì vậy nếu thư mục hiện tại đã thay đổi và $PWD
không còn trỏ đến thư mục hiện tại, nó thực sự sẽ không thay đổi a
thư mục trong thư mục hiện tại , nhưng lỗi trong $PWD
(và trả về lỗi nếu nó không tồn tại).
Nếu bạn muốn chơi với nó, bạn có thể làm:
cd
mkdir -p a/b
cd a
pwd
mv ~/a ~/b
pwd
echo "$PWD"
cd b
pwd; echo "$PWD"; pwd -P # (and notice the bug in ksh93)
trong các vỏ khác nhau.
Trong trường hợp của bạn, vì bạn đang sử dụng bash
, sau một cd a
, bash
kiểm tra xem $PWD
vẫn còn trỏ đến thư mục hiện tại. Để làm điều đó, nó gọi stat()
giá trị của $PWD
để kiểm tra số inode của nó và so sánh nó với giá trị của .
.
Nhưng khi tìm kiếm $PWD
đường dẫn liên quan đến việc giải quyết quá nhiều liên kết tượng trưng, stat()
sẽ trả về một lỗi, vì vậy trình bao không thể kiểm tra xem có $PWD
còn tương ứng với thư mục hiện tại hay không, do đó, nó sẽ tính toán lại getcwd()
và cập nhật $PWD
tương ứng.
Bây giờ, để làm rõ câu trả lời của Patrice, việc kiểm tra số lượng liên kết tượng trưng gặp phải trong khi tìm đường dẫn là để bảo vệ chống lại các vòng lặp liên kết. Vòng lặp đơn giản nhất có thể được thực hiện với
rm -f a b
ln -s a b
ln -s b a
Nếu không có sự bảo vệ an toàn đó, theo một cd a/x
, hệ thống sẽ phải tìm nơi a
liên kết đến, tìm thấy nó b
và là một liên kết tượng trưng liên kết đến a
, và điều đó sẽ tiếp diễn vô tận. Cách đơn giản nhất để bảo vệ chống lại điều đó là từ bỏ sau khi giải quyết nhiều hơn một số liên kết tượng trưng tùy ý.
Bây giờ trở lại thư mục làm việc hiện tại hợp lý và tại sao nó không phải là một tính năng tốt. Điều quan trọng là phải nhận ra rằng nó chỉ dành cho cd
shell chứ không phải các lệnh khác.
Ví dụ:
cd -- "$dir" && vi -- "$file"
không phải lúc nào cũng giống như:
vi -- "$dir/$file"
Đó là lý do tại sao đôi khi bạn sẽ thấy rằng mọi người khuyên bạn nên luôn sử dụng cd -P
các tập lệnh để tránh nhầm lẫn (bạn không muốn phần mềm của mình xử lý một đối số ../x
khác với các lệnh khác chỉ vì nó được viết bằng vỏ thay vì ngôn ngữ khác).
Các -P
lựa chọn là để vô hiệu hóa các thư mục logic xử lý để cd -P -- "$var"
thực hiện gọi chdir()
về nội dung của $var
(trừ khi $var
là -
nhưng đó là một câu chuyện khác). Và sau một cd -P
, $PWD
sẽ chứa một con đường chính tắc.