प्रतीकात्मक लिंक पुनरावृत्ति - यह "रीसेट" क्या करता है?


64

मैंने यह देखने के लिए थोड़ा बैश स्क्रिप्ट लिखी कि क्या होता है जब मैं एक प्रतीकात्मक लिंक का अनुसरण करता रहता हूं जो एक ही निर्देशिका को इंगित करता है। मैं यह उम्मीद कर रहा था कि या तो बहुत लंबे समय तक काम करने वाली निर्देशिका बनाऊँ, या दुर्घटनाग्रस्त हो जाऊँ। लेकिन परिणाम ने मुझे चौंका दिया ...

mkdir a
cd a

ln -s ./. a

for i in `seq 1 1000`
do
  cd a
  pwd
done

कुछ आउटपुट है

${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

यहाँ क्या हो रहा है?

जवाबों:


88

पैट्रिस ने अपने उत्तर में समस्या के स्रोत की पहचान की , लेकिन अगर आप जानना चाहते हैं कि वहां से कैसे प्राप्त किया जाए तो आप इसे प्राप्त करते हैं, तो यहां एक लंबी कहानी है।

एक प्रक्रिया की वर्तमान कार्यशील निर्देशिका कुछ भी नहीं है जिसे आप बहुत जटिल समझेंगे। यह उस प्रक्रिया का एक गुण है, जो एक प्रकार की निर्देशिका की फ़ाइल के लिए एक हैंडल है, जहां रिश्तेदार पथ (प्रक्रिया द्वारा किए गए सिस्टम कॉल) से शुरू होते हैं। किसी सापेक्ष पथ को हल करते समय, कर्नेल को उस वर्तमान निर्देशिका के लिए (a) पूर्ण पथ जानने की आवश्यकता नहीं होती है, यह केवल सापेक्ष पथ के पहले घटक को खोजने के लिए उस निर्देशिका फ़ाइल में निर्देशिका प्रविष्टियों को पढ़ता है (और ..किसी अन्य की तरह है) उस संबंध में फ़ाइल) और वहाँ से जारी है।

अब, एक उपयोगकर्ता के रूप में, आप कभी-कभी यह जानना पसंद करते हैं कि निर्देशिका ट्री में कहाँ है। अधिकांश यूनियनों के साथ, निर्देशिका पेड़ एक पेड़ है, जिसमें कोई लूप नहीं है। यही है, पेड़ की जड़ ( /) से किसी भी फ़ाइल में केवल एक ही रास्ता है । उस पथ को आम तौर पर विहित पथ कहा जाता है।

वर्तमान कार्यशील निर्देशिका का मार्ग प्राप्त करने के लिए, एक प्रक्रिया को करने के लिए बस चलना है (अच्छी तरह से नीचे अगर आप नीचे एक पेड़ को उसकी जड़ के साथ देखना पसंद करते हैं) पेड़ को जड़ तक वापस जाना, नोड्स के नाम खोजना रास्ते में।

उदाहरण के लिए, एक प्रक्रिया जो यह पता लगाने की कोशिश कर रही है कि इसकी वर्तमान निर्देशिका है /a/b/c, निर्देशिका को खोलेगी ..(सापेक्ष पथ, इसलिए ..वर्तमान निर्देशिका में प्रविष्टि है) और उसी इनोड संख्या के साथ टाइप निर्देशिका की एक फ़ाइल की .तलाश करें, यह पता करें कि cमैच, तब तक खुलता है ../..और जब तक यह नहीं मिलता है /। वहां कोई अस्पष्टता नहीं है।

यह वही है जो getwd()या getcwd()सी कार्य करता है या कम से कम करने के लिए उपयोग किया जाता है।

आधुनिक लिनक्स जैसी कुछ प्रणालियों पर, वर्तमान निर्देशिका में कैनोनिकल पथ को वापस करने के लिए एक सिस्टम कॉल है जो कर्नेल स्पेस में उस लुकअप को करता है (और आपको अपने वर्तमान निर्देशिका को खोजने की अनुमति देता है, भले ही आपने इसके सभी घटकों तक पहुंच न पढ़ी हो) , और यही getcwd()वहां कॉल है। आधुनिक लिनक्स पर, आप एक रीडलिंक () के माध्यम से वर्तमान निर्देशिका का पथ भी पा सकते हैं /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सम्‍मिलित हैं /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नाम बदला जा सकता है। वर्तमान निर्देशिका कभी नहीं बदलती (यह हमेशा एक ही इनोड है, हालांकि इसे हटाया जा सकता है), लेकिन निर्देशिका ट्री में इसका पथ पूरी तरह से बदल सकता है। getcwd()वर्तमान निर्देशिका की गणना हर बार निर्देशिका पेड़ के नीचे चलने से होती है, इसलिए इसकी जानकारी हमेशा सटीक होती है, लेकिन POSIX गोले द्वारा लागू की गई तार्किक निर्देशिका के लिए, जानकारी $PWDबासी हो सकती है। इसलिए दौड़ने पर cdया pwd, कुछ गोले उस पर पहरा देना चाहते हैं।

उस विशेष उदाहरण में, आप अलग-अलग गोले के साथ अलग-अलग व्यवहार देखते हैं।

कुछ ksh93लोग समस्या को पूरी तरह से अनदेखा कर देते हैं, इसलिए आपके कॉल करने पर भी गलत जानकारी वापस आ जाएगी cd(और आप उस व्यवहार को नहीं देखेंगे जो आप bashवहाँ देख रहे हैं )।

कुछ लोग ऐसा करते हैं bashया zshजाँचते हैं कि $PWDअभी भी चालू निर्देशिका के लिए एक रास्ता है cd, लेकिन नहीं pwd

pdksh दोनों पर जाँच करता है pwdऔर cd(लेकिन pwdअपडेट नहीं करता है $PWD)

ash(कम से कम एक डेबियन पर होता है) की जांच नहीं करता है, और जब आप ऐसा करेंगे 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लिए मूल्य पर कॉल करता है .

लेकिन जब $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एक विहित पथ होगा।


7
मधुर यीशु! इस तरह के एक व्यापक जवाब के लिए धन्यवाद, यह वास्तव में काफी दिलचस्प है :)
लुकास

बहुत बढ़िया जवाब, बहुत बहुत धन्यवाद! मुझे लगता है कि मैं थोड़े इन सभी चीजों को जानता था, लेकिन मैं कभी नहीं समझा या सोचा नहीं था कि वे सभी एक साथ कैसे आए। महान व्याख्या।
dimo414

42

यह लिनक्स कर्नेल स्रोत में हार्ड-कोडित सीमा का परिणाम है; सेवा से वंचित करने से रोकने के लिए, नेस्टेड सिम्लिंक की संख्या 40 है ( कर्नेल स्रोत द्वारा कहा जाता है, अंदर follow_link()फ़ंक्शन में पाया fs/namei.cजाता है nested_symlink())।

आपको संभवतः सहानुभूति का समर्थन करने वाले अन्य कर्नेल के साथ एक समान व्यवहार (और संभवतः 40 की तुलना में एक और सीमा) मिलेगा।


1
क्या इसके लिए "रीसेट" करने का एक कारण है, न कि केवल रोकना। के x%40बजाय max(x,40)। मुझे लगता है कि आप अब भी देख सकते हैं कि आपने निर्देशिका बदल दी है।
लुकास

4
स्रोत का एक लिंक, किसी और के लिए उत्सुक: lxr.linux.no/linux+v3.9.6/fs/namei.c#L818
बेन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.