Saya menulis skrip bash kecil untuk melihat apa yang terjadi ketika saya terus mengikuti tautan simbolis yang menunjuk ke direktori yang sama. Saya mengharapkannya untuk membuat direktori kerja yang sangat panjang, atau macet. Tapi hasilnya mengejutkan saya…
mkdir a
cd a
ln -s ./. a
for i in `seq 1 1000`
do
cd a
pwd
done
Beberapa outputnya adalah
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a
${HOME}/a/a
${HOME}/a/a/a
${HOME}/a/a/a/a
${HOME}/a/a/a/a/a
${HOME}/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a
apa yang terjadi di sini?
Jawaban yang Diterima:
Patrice mengidentifikasi sumber masalah dalam jawabannya, tetapi jika Anda ingin tahu bagaimana pergi dari sana dan mengapa Anda mendapatkannya, inilah cerita panjangnya.
Direktori kerja saat ini dari suatu proses bukanlah hal yang menurut Anda terlalu rumit. Ini adalah atribut dari proses yang merupakan pegangan ke file dari direktori tipe di mana jalur relatif (dalam panggilan sistem yang dibuat oleh proses) dimulai. Saat menyelesaikan jalur relatif, kernel tidak perlu mengetahui (a) jalur lengkap ke direktori saat ini, kernel hanya membaca entri direktori dalam file direktori tersebut untuk menemukan komponen pertama dari jalur relatif (dan ..
seperti file lain dalam hal itu) dan berlanjut dari sana.
Sekarang, sebagai pengguna, Anda terkadang ingin tahu di mana letak direktori itu di pohon direktori. Dengan sebagian besar Unices, pohon direktori adalah pohon, tanpa loop. Artinya, hanya ada satu jalur dari akar pohon (/
) ke file apa pun. Jalur itu umumnya disebut jalur kanonik.
Untuk mendapatkan jalur direktori kerja saat ini, proses yang harus dilakukan hanyalah berjalan ke atas (well bawah jika Anda ingin melihat pohon dengan akarnya di bawah) pohon kembali ke akarnya, temukan nama-nama simpul di jalan.
Misalnya, sebuah proses mencoba untuk mengetahui bahwa direktori saat ini adalah /a/b/c
, akan membuka ..
direktori (jalur relatif, jadi ..
adalah entri dalam direktori saat ini) dan cari file dengan tipe direktori dengan nomor inode yang sama dengan .
, cari tahu bahwa c
cocok, lalu buka ../..
dan seterusnya sampai menemukan /
. Tidak ada ambiguitas di sana.
Itulah yang getwd()
atau getcwd()
Fungsi C melakukan atau setidaknya dulu melakukan.
Pada beberapa sistem seperti Linux modern, ada panggilan sistem untuk mengembalikan jalur kanonik ke direktori saat ini yang melakukan pencarian itu di ruang kernel (dan memungkinkan Anda menemukan direktori saat ini bahkan jika Anda tidak memiliki akses baca ke semua komponennya) , dan itulah yang getcwd()
panggilan di sana. Di Linux modern, Anda juga dapat menemukan jalur ke direktori saat ini melalui readlink() di /proc/self/cwd
.
Itulah yang dilakukan sebagian besar bahasa dan shell awal saat mengembalikan jalur ke direktori saat ini.
Dalam kasus Anda, Anda dapat memanggil cd a
sesering yang Anda inginkan, karena ini adalah symlink ke .
, direktori saat ini tidak berubah sehingga semua getcwd()
, pwd -P
, python -c 'import os; print os.getcwd()'
, perl -MPOSIX -le 'print getcwd'
akan mengembalikan ${HOME}
. Anda .
Sekarang, symlink memperumit semua itu.
symlinks
izinkan lompatan di pohon direktori. Dalam /a/b/c
, jika /a
atau /a/b
atau /a/b/c
adalah symlink, maka jalur kanonik /a/b/c
akan menjadi sesuatu yang sama sekali berbeda. Khususnya, ..
entri di /a/b/c
belum tentu /a/b
.
Di shell Bourne, jika Anda melakukannya:
cd /a/b/c
cd ..
Atau bahkan:
cd /a/b/c/..
Tidak ada jaminan Anda akan berakhir di /a/b
.
Seperti:
vi /a/b/c/../d
belum tentu sama dengan:
vi /a/b/d
ksh
memperkenalkan konsep direktori kerja logis saat ini entah bagaimana bekerja di sekitar itu. Orang-orang menjadi terbiasa dan POSIX akhirnya menentukan perilaku itu yang berarti sebagian besar shell saat ini juga melakukannya:
Untuk cd
dan pwd
perintah bawaan (dan hanya untuk mereka (meskipun juga untuk popd
/pushd
pada shell yang memilikinya)), shell mempertahankan idenya sendiri tentang direktori kerja saat ini. Itu disimpan di $PWD
variabel khusus.
Ketika Anda melakukannya:
cd c/d
meskipun c
atau c/d
adalah symlink, sedangkan $PWD
berisi /a/b
, itu menambahkan c/d
sampai akhir jadi $PWD
menjadi /a/b/c/d
. Dan ketika Anda melakukannya:
cd ../e
Alih-alih melakukan chdir("../e")
, itu chdir("/a/b/c/e")
.
Dan pwd
perintah hanya mengembalikan konten $PWD
variabel.
Itu berguna dalam shell interaktif karena pwd
menampilkan jalur ke direktori saat ini yang memberikan informasi tentang bagaimana Anda sampai di sana dan selama Anda hanya menggunakan ..
dalam argumen ke cd
dan bukan perintah lain, kemungkinan kecil Anda akan terkejut, karena cd a; cd ..
atau cd a/..
biasanya akan membawa Anda kembali ke tempat Anda sebelumnya.
Sekarang, $PWD
tidak dimodifikasi kecuali Anda melakukan cd
. Sampai waktu berikutnya Anda menelepon cd
atau pwd
, banyak hal bisa terjadi, salah satu komponen $PWD
bisa diganti namanya. Direktori saat ini tidak pernah berubah (selalu inode yang sama, meskipun dapat dihapus), tetapi jalurnya di pohon direktori dapat berubah sepenuhnya. getcwd()
menghitung direktori saat ini setiap kali dipanggil dengan menelusuri pohon direktori sehingga informasinya selalu akurat, tetapi untuk direktori logis yang diimplementasikan oleh shell POSIX, informasi di $PWD
mungkin menjadi basi. Jadi setelah menjalankan cd
atau pwd
, beberapa shell mungkin ingin mencegahnya.
Dalam contoh khusus itu, Anda melihat perilaku yang berbeda dengan cangkang yang berbeda.
Beberapa seperti ksh93
abaikan masalah sepenuhnya, sehingga akan mengembalikan informasi yang salah bahkan setelah Anda memanggil cd
(dan Anda tidak akan melihat perilaku yang Anda lihat dengan bash
sana).
Beberapa menyukai bash
atau zsh
periksa apakah $PWD
masih merupakan jalur ke direktori saat ini pada cd
, tetapi tidak pada pwd
.
pdksh memeriksa keduanya pwd
dan cd
(tetapi setelah pwd
, tidak memperbarui $PWD
)
ash
(setidaknya yang ditemukan di Debian) tidak memeriksa, dan ketika Anda melakukan cd a
, sebenarnya cd "$PWD/a"
, jadi jika direktori saat ini telah berubah dan $PWD
tidak lagi menunjuk ke direktori saat ini, itu sebenarnya tidak akan berubah menjadi a
direktori di direktori saat ini, tetapi yang ada di $PWD
(dan kembalikan kesalahan jika tidak ada).
Jika Anda ingin bermain dengannya, Anda dapat melakukan:
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)
dalam berbagai cangkang.
Dalam kasus Anda, karena Anda menggunakan bash
, setelah cd a
, bash
memeriksa bahwa $PWD
masih menunjuk ke direktori saat ini. Untuk melakukan itu, ia memanggil stat()
pada nilai $PWD
untuk memeriksa nomor inodenya dan membandingkannya dengan .
.
Tetapi ketika mencari $PWD
path melibatkan penyelesaian terlalu banyak symlink, yang stat()
kembali dengan kesalahan, sehingga shell tidak dapat memeriksa apakah $PWD
masih sesuai dengan direktori saat ini, sehingga menghitungnya lagi dengan getcwd()
dan memperbarui $PWD
sesuai.
Sekarang, untuk memperjelas jawaban Patrice, pemeriksaan jumlah symlink yang ditemui saat mencari jalur adalah untuk menjaga dari loop symlink. Loop paling sederhana dapat dibuat dengan
rm -f a b
ln -s a b
ln -s b a
Tanpa pengaman itu, di atas cd a/x
, sistem harus menemukan di mana a
link ke, menemukan itu b
dan merupakan symlink yang tertaut ke a
, dan itu akan berlangsung tanpa batas. Cara paling sederhana untuk menghindarinya adalah dengan menyerah setelah menyelesaikan lebih dari jumlah symlink yang berubah-ubah.
Sekarang kembali ke direktori kerja logis saat ini dan mengapa itu bukan fitur yang bagus. Penting untuk disadari bahwa ini hanya untuk cd
di shell dan bukan perintah lain.
Misalnya:
cd -- "$dir" && vi -- "$file"
tidak selalu sama dengan:
vi -- "$dir/$file"
Itulah mengapa Anda terkadang menemukan bahwa orang menyarankan untuk selalu menggunakan cd -P
dalam skrip untuk menghindari kebingungan (Anda tidak ingin perangkat lunak Anda menangani argumen ../x
berbeda dari perintah lain hanya karena ditulis dalam shell, bukan bahasa lain).
-P
pilihannya adalah menonaktifkan direktori logis menangani jadi cd -P -- "$var"
sebenarnya memanggil chdir()
pada konten $var
(setidaknya selama $CDPATH
itu tidak disetel, dan kecuali ketika $var
adalah -
(atau mungkin -2
, +3
... di beberapa cangkang) tapi itu cerita lain). Dan setelah cd -P
, $PWD
akan berisi jalur kanonik.