Патріс визначив джерело проблеми у своїй відповіді , але якщо ви хочете знати, як звідти дійти до того, чому ви це отримаєте, ось довга історія.
Поточний робочий каталог процесу - це те, що ви вважаєте занадто складним. Це атрибут процесу, який є ручкою до файлу каталогу типу, з якого починаються відносні шляхи (у системних викликах, здійснених процесом). При вирішенні відносного шляху ядро не потрібно знати повний шлях (a) до цього поточного каталогу, воно просто зчитує записи каталогів у цьому файлі каталогу, щоб знайти перший компонент відносного шляху (і ..
це як і будь-який інший файл з цього приводу) і продовжується звідти.
Тепер, як користувач, вам іноді подобається знати, де знаходиться цей каталог у дереві каталогів. У більшості Unices дерево каталогів - це дерево, без циклу. Тобто, є лише один шлях від кореня дерева ( /
) до будь-якого файлу. Цей шлях взагалі називають канонічним шляхом.
Щоб отримати шлях до поточного робочого каталогу, потрібно зробити лише процес вгору (добре вниз, якщо ви хочете бачити дерево з його коренем внизу) дерево назад до кореня, знаходячи назви вузлів на шляху.
Наприклад, процес, який намагається з'ясувати, що це його поточний каталог /a/b/c
, відкриє ..
каталог (відносний шлях, такий ..
є запис у поточному каталозі) та шукатиме файл типу каталогів з тим самим номером вводу .
, що і з'ясувати, що c
збігається, потім відкривається ../..
і так далі, поки не знайде /
. Там немає двозначності.
Ось що роблять або, принаймні, раніше виконують функції getwd()
або getcwd()
C.
У деяких системах, таких як сучасний Linux, є системний виклик, щоб повернути канонічний шлях до поточного каталогу, який здійснює пошук у просторі ядра (і дозволяє знайти поточний каталог, навіть якщо ви не маєте доступу для читання до всіх його компонентів) і ось що getcwd()
дзвонить туди. У сучасному Linux ви також можете знайти шлях до поточного каталогу через readlink () на /proc/self/cwd
.
Саме це робить більшість мов та ранніх оболонок, повертаючи шлях до поточного каталогу.
У вашому випадку, ви можете зателефонувати , cd a
як може раз , як ви хочете, тому що це символьне посилання .
, поточний каталог не змінюється , так все getcwd()
, pwd -P
, python -c 'import os; print os.getcwd()'
, perl -MPOSIX -le 'print getcwd'
повернеться ваш ${HOME}
.
Тепер символьні посилання ускладнювали все це.
symlinks
дозволити стрибки в дереві каталогів. В /a/b/c
, якщо /a
або /a/b
або /a/b/c
є символічним посиланням, то канонічний шлях /a/b/c
буде що - то зовсім інше. Зокрема, ..
запис /a/b/c
не обов'язково /a/b
.
У оболонці Борна, якщо ви робите:
cd /a/b/c
cd ..
Або навіть:
cd /a/b/c/..
Немає гарантій, що ти опинишся /a/b
.
Так як:
vi /a/b/c/../d
не обов'язково те саме, що:
vi /a/b/d
ksh
ввели концепцію логічного поточного робочого каталогу, щоб якось обійти це. Люди звикли до цього, і POSIX в кінці кінців уточнив таку поведінку, яка означає, що більшість снарядів і сьогодні це роблять:
Для команд cd
і pwd
вбудованих команд ( і лише для них (хоча також для popd
/ pushd
на оболонках, які їх мають)) оболонка підтримує власне уявлення про поточний робочий каталог. Він зберігається в $PWD
спеціальній змінній.
Коли ви робите:
cd c/d
навіть якщо c
і c/d
символічні посилання, в той час як $PWD
containes /a/b
, він додає c/d
до кінця так $PWD
стає /a/b/c/d
. А коли ви робите:
cd ../e
Замість того, щоб робити chdir("../e")
, це робить chdir("/a/b/c/e")
.
І pwd
команда лише повертає вміст $PWD
змінної.
Це корисно в інтерактивних оболонках, оскільки pwd
виводить шлях до поточного каталогу, який дає інформацію про те, як ви потрапили туди, і поки ви використовуєте лише ..
аргументи до, cd
а не інших команд, менша ймовірність вас здивує, оскільки cd a; cd ..
або cd a/..
взагалі поверне вас назад туди, де ти був.
Тепер, $PWD
не змінюється, якщо ви не зробите це cd
. До наступного виклику cd
або pwd
, можливо, багато чого може статися, будь-який із компонентів системи $PWD
може бути перейменований. Поточний каталог ніколи не змінюється (це завжди той самий inode, хоча його можна видалити), але його шлях у дереві каталогів може повністю змінитися. getcwd()
обчислює поточний каталог кожен раз, коли він викликається, йдучи по дереву каталогів, тому його інформація завжди точна, але для логічного каталогу, реалізованого оболонками POSIX, інформація в $PWD
обробці може стати застарілою. Тож, бігаючи cd
або pwd
, деякі снаряди можуть захотіти захиститись від цього.
У цьому конкретному випадку ви бачите різну поведінку з різними оболонками.
Деякі люблять ksh93
ігнорувати проблему повністю, тому повертають невірну інформацію навіть після дзвінка cd
(і ви б не бачили поведінки, яку ви бачите bash
там).
Деякі на кшталт bash
або zsh
перевіряють, що $PWD
це все ще шлях до поточного каталогу cd
, але не на pwd
.
pdksh перевіряє і те, pwd
і cd
(але після pwd
, не оновлює $PWD
)
ash
(принаймні той, який знайдено на Debian) не перевіряє, і коли ви це зробите cd a
, він насправді робить cd "$PWD/a"
, тому, якщо поточний каталог змінився і $PWD
більше не вказує на поточний каталог, він фактично не зміниться на a
каталог у поточному каталозі , але один в $PWD
(і повернути помилку, якщо її немає).
Якщо ви хочете пограти з ним, ви можете зробити:
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)
в різних оболонках.
У вашому випадку, оскільки ви використовуєте bash
після cd a
, bash
перевіряє, що $PWD
все ще вказує на поточний каталог. Для цього він закликає stat()
значення $PWD
перевірити його номер inode та порівняти його зі значенням .
.
Але коли пошук $PWD
шляху передбачає вирішення занадто багато символьних посилань, це stat()
повертається з помилкою, тому оболонка не може перевірити, чи $PWD
все ще відповідає поточному каталогу, тому він обчислює його ще раз getcwd()
і оновлює $PWD
відповідно.
Тепер, щоб уточнити відповідь Патріса, перевірка кількості посилань, що виникають під час пошуку шляху, полягає у захисті від циклів символьних посилань. Найпростішу петлю можна зробити за допомогою
rm -f a b
ln -s a b
ln -s b a
Без цього безпечного охоронного cd a/x
пристрою система повинна буде знаходити, де a
посилається, знаходить це b
і є символьним посиланням, на яке посилається a
, і це буде тривати нескінченно. Найпростіший спосіб уберегтися від цього - відмовитися після вирішення більше ніж довільну кількість символьних посилань.
Тепер повернемося до логічного поточного робочого каталогу і чому це не така гарна функція. Важливо усвідомити, що це лише для cd
оболонки, а не для інших команд.
Наприклад:
cd -- "$dir" && vi -- "$file"
не завжди те саме, що:
vi -- "$dir/$file"
Ось чому іноді ви виявите, що люди рекомендують завжди використовувати cd -P
в скриптах, щоб уникнути плутанини (ви не хочете, щоб ваше програмне забезпечення обробляло аргументи, що ../x
відрізняються від інших команд лише тому, що воно написано оболонкою замість іншої мови).
-P
Опція для відключення логічного каталогу обробки , так на cd -P -- "$var"
самому справі дзвонити chdir()
за змістом $var
(крім випадків , коли $var
це , -
але це інша історія). І після того cd -P
, $PWD
буде містити канонічний шлях.