भविष्य में बदले जाने पर नुकसान पहुंचाने के खिलाफ मैं स्क्रिप्ट को कैसे सख्त कर सकता हूं?


46

इसलिए, मैंने अपना होम फोल्डर (या, अधिक सटीक रूप से, उन सभी फाइलों को डिलीट कर दिया है, जिनकी पहुंच मैंने लिखी थी)। जो हुआ वो मेरे पास था

build="build"
...
rm -rf "${build}/"*
...
<do other things with $build>

एक बैश स्क्रिप्ट में और, अब आवश्यकता के बाद $build, घोषणा और उसके सभी उपयोगों को हटाने - लेकिन rm। बैश खुशी से फैलता है rm -rf /*। हाँ।

मैंने बेवकूफ महसूस किया, बैकअप स्थापित किया, जो काम मैंने खो दिया, उसे फिर से किया। शर्म से अतीत को आगे बढ़ाने की कोशिश की जा रही है।

अब, मुझे आश्चर्य है: ऐसी तकनीकें जो बैश स्क्रिप्ट लिखने के लिए हैं ताकि ऐसी गलतियाँ न हो सकें, या कम से कम संभावना हो? उदाहरण के लिए, मैंने लिखा था

FileUtils.rm_rf("#{build}/*")

रूबी लिपि में, दुभाषिया buildने घोषित नहीं होने के बारे में शिकायत की होगी , इसलिए वहां भाषा मेरी रक्षा करती है।

मैंने बैश में विचार किया है, इसके अलावा कोरिलिंग rm(जो संबंधित प्रश्नों के कई उत्तरों का उल्लेख करता है, अप्रमाणिक नहीं है):

  1. rm -rf "./${build}/"*
    इसने मेरे वर्तमान कार्य (एक Git रेपो) को मार दिया होगा लेकिन कुछ और नहीं।
  2. rmवर्तमान निर्देशिका के बाहर कार्य करते समय उस के एक प्रकार / परिमाणीकरण को सहभागिता की आवश्यकता होती है। (कोई भी नहीं मिल सका।) समान प्रभाव।

क्या यह है, या इस अर्थ में "मजबूत" हैं बैश स्क्रिप्ट लिखने के अन्य तरीके हैं?


3
वहाँ के लिए कोई कारण नहीं है rm -rf "${build}/*, भले ही वह उद्धरण चिह्न जाना। rm -rf "${build} की वजह से एक ही काम करेंगे f
मोंटी हार्डर

1
Shellcheck.net के साथ स्टेटिक चेकिंग शुरू करने के लिए एक बहुत ही ठोस जगह है। वहाँ संपादक एकीकरण उपलब्ध है, इसलिए जैसे ही आप अभी भी उपयोग की जाने वाली किसी चीज़ की परिभाषा को हटाते हैं, आप अपने टूल से चेतावनी प्राप्त कर सकते हैं।
चार्ल्स डफी

मेरी शर्म की बात है के लिए @CharlesDuffy में जोड़े, मेरे पास एक सुझाव शैली आईडीई BashSupport स्थापित है, जिसमें कि पटकथा लिखी है उस मामले में चेतावनी दी है। तो हाँ, मान्य बिंदु, लेकिन मुझे वास्तव में एक कठिन रद्द करने की आवश्यकता थी।
राफेल

2
पकड़ लिया। BashFAQ # 112 में चेतावनी पर ध्यान दें । set -uके रूप में लगभग के रूप में frowned नहीं set -eहै, लेकिन यह अभी भी अपने gotchas है।
चार्ल्स डफी

2
मज़ाक का जवाब: बस #! /usr/bin/env rubyहर खोल स्क्रिप्ट के शीर्ष पर रखा और बैश के बारे में भूल;)
पॉड

जवाबों:


75
set -u

या

set -o nounset

यह वर्तमान शेल को एक त्रुटि के रूप में unset चर के विस्तार का इलाज करेगा:

$ unset build
$ set -u
$ rm -rf "$build"/*
bash: build: unbound variable

set -uऔर set -o nounsetकर रहे हैं POSIX खोल विकल्प

एक खाली मान हालांकि एक त्रुटि को ट्रिगर नहीं करेगा ।

उसके लिए, का उपयोग करें

$ rm -rf "${build:?Error, variable is empty or unset}"/*
bash: build: Error, variable is empty or unset

के विस्तार का ${variable:?word}मूल्य variableतब तक बढ़ेगा जब तक कि यह खाली या परेशान न हो। यदि यह खाली या परेशान है, तो wordमानक त्रुटि पर प्रदर्शित किया जाएगा और शेल एक त्रुटि के रूप में विस्तार का इलाज करेगा (कमांड निष्पादित नहीं किया जाएगा, और यदि गैर-इंटरैक्टिव शेल में चल रहा है, तो यह समाप्त हो जाएगा)। :बाहर छोड़ना केवल एक अनसेट वैल्यू के लिए त्रुटि को ट्रिगर करेगा, जैसे कि नीचे set -u

${variable:?word}एक POSIX पैरामीटर विस्तार है

जब तक set -e(या set -o errexit) प्रभाव में नहीं था, तब तक इनमें से कोई भी एक इंटरैक्टिव शेल को समाप्त नहीं करेगा । ${variable:?word}यदि स्क्रिप्ट खाली या अशांत है तो बाहर निकलने के लिए स्क्रिप्ट का कारण बनता है। set -uयदि स्क्रिप्ट को एक साथ उपयोग करने के लिए बाहर निकलने का कारण होगा set -e


अपने दूसरे प्रश्न के रूप में। rmवर्तमान निर्देशिका के बाहर काम न करने के लिए सीमित करने का कोई तरीका नहीं है।

GNU कार्यान्वयन के rmपास एक --one-file-systemविकल्प है जो इसे पुन: माउंटेड फाइल सिस्टम को हटाने से रोकता है, लेकिन यह उतना ही निकट है जितना कि मेरा मानना ​​है कि हम rmकिसी फ़ंक्शन में कॉल को लपेटे बिना प्राप्त कर सकते हैं जो वास्तव में तर्कों की जांच करता है।


एक साइड नोट के रूप में: जब तक विस्तार एक स्ट्रिंग के हिस्से के रूप में नहीं होता, तब तक इसके ${build}बराबर $buildहोता है, जहां तुरंत निम्नलिखित चरित्र एक चर नाम में एक मान्य वर्ण है, जैसे कि "${build}x"


बहुत अच्छा धन्यवाद! 1) यह ${build:?msg}या है ${build?msg}? 2) बिल्ड स्क्रिप्ट जैसी किसी चीज के संदर्भ में, मुझे लगता है कि सुरक्षित विलोपन के लिए rm की तुलना में एक अलग टूल का उपयोग करना ठीक होगा: हम जानते हैं कि हम वर्तमान निर्देशिका के बाहर काम नहीं करना चाहते हैं, इसलिए हम एक प्रतिबंधित का उपयोग करके इसे स्पष्ट करते हैं आदेश। Rm को सामान्य रूप से सुरक्षित बनाने की कोई आवश्यकता नहीं है।
राफेल

1
@ राफेल क्षमा करें, साथ होना चाहिए:। अब मैं उस टाइपो को ठीक करूँगा और उसके महत्व का उल्लेख बाद में करूँगा जब मैं अपने कंप्यूटर पर वापस आऊँगा।
Kusalananda

2
@ राफेल ओके, मैंने एक छोटा वाक्य जोड़ा है कि इसके बिना क्या होता है :(यह केवल परेशान चर के लिए त्रुटि को ट्रिगर करेगा )। मैं एक ऐसे उपकरण को लिखने की कोशिश नहीं करता जो सभी परिस्थितियों में विशेष रूप से एक विशिष्ट पथ के तहत फ़ाइलों को हटाने के साथ सामना करेगा। पार्सिंग पथ और प्रतीकात्मक लिंक के बारे में देखभाल / देखभाल नहीं करना मेरे लिए इस समय बहुत ही उचित है।
Kusalananda

1
धन्यवाद, स्पष्टीकरण मदद करता है! "स्थानीय आरएम" के बारे में: मैं ज्यादातर पेशी कर रहा था, शायद मछली पकड़ने के लिए "यकीन है, कि <toolname>" है - लेकिन निश्चित रूप से आप इसे लिखने में मदद करने की कोशिश नहीं कर रहे हैं! ऊ सब अच्छा है, आप पर्याप्त मदद की है! :)
राफेल

2
@MateuszKonieczny मुझे नहीं लगता कि मैं माफी चाहता हूँ। मेरा मानना ​​है कि जरूरत पड़ने पर इन जैसे सुरक्षा उपायों का इस्तेमाल किया जाना चाहिए । पर्यावरण को सुरक्षित बनाने वाली हर चीज के साथ, यह अंततः लोगों को अधिक से अधिक लापरवाह बना देगा और सुरक्षा उपायों पर निर्भर करेगा। यह जानना बेहतर है कि प्रत्येक सुरक्षा उपाय क्या करता है और फिर उन्हें आवश्यकतानुसार चुनिए।
Kusalananda

12

मैं सामान्य सत्यापन जांच का उपयोग करने का सुझाव देने जा रहा हूं test/[ ]

यदि आप अपनी स्क्रिप्ट को इस प्रकार लिखते तो आप सुरक्षित होते:

build="build"
...
[ -n "${build}" ] || exit 1
rm -rf "${build}/"*
...

[ -n "${build}" ]जांच करता है कि "${build}"एक है गैर शून्य लंबाई स्ट्रिंग

||तार्किक या बैश में ऑपरेटर है। यह एक और कमांड चलाने का कारण बनता है अगर पहले वाला विफल रहा।

इस तरह, ${build}खाली / अपरिभाषित / आदि हो गया था। स्क्रिप्ट बाहर निकल गई होगी (1 के रिटर्न कोड के साथ, जो एक सामान्य त्रुटि है)।

यह भी आप संरक्षित होता मामले में आप सभी का उपयोग करता है हटा दिया ${build}क्योंकि [ -n "" ]हमेशा गलत हो जाएगा।


उपयोग करने का लाभ test/ [ ]है और भी कई अर्थपूर्ण जाँचें हैं जिनका उपयोग यह भी कर सकता है।

उदाहरण के लिए:

[ -f FILE ] True if FILE exists and is a regular file.
[ -d FILE ] True if FILE exists and is a directory.
[ -O FILE ] True if FILE exists and is owned by the effective user ID.

साफ, लेकिन एक बालक undieldy। क्या मुझे कुछ याद आ रहा है, या यह ${variable:?word}@Kusalananda के प्रस्ताव के रूप में बहुत कुछ करता है ?
राफेल

@ रिपेल यह उसी तरह काम करता है जैसे "स्ट्रिंग का मान होता है" लेकिन test(यानी [) में कई अन्य जांचें हैं जो प्रासंगिक हैं, जैसे -d(तर्क एक निर्देशिका है), -f(तर्क एक फ़ाइल है), -O(फ़ाइल वर्तमान उपयोगकर्ता के स्वामित्व में है) और इतने पर।
सेंटिमेन

1
@ राफेल भी, सत्यापन किसी भी कोड / स्क्रिप्ट का एक सामान्य हिस्सा होना चाहिए। यह भी ध्यान दें, यदि आपने निर्माण के सभी उदाहरणों को हटा दिया है, तो क्या आपने इसके ${build:?word}साथ भी नहीं हटाया होगा? ${variable:?word}प्रारूप यदि आप चर की सभी आवृत्तियां निकाल आप की रक्षा नहीं करता।
सेंटिमेन

1
1) मुझे लगता है कि निश्चित मान्यताओं को बनाए रखने (फ़ाइलों के मौजूद होने, आदि) और यदि चर सेट हैं (कंपाइलर / दुभाषिया का imho काम) की जाँच करने के बीच अंतर है। यदि मुझे हाथ से बाद करना है, तो इसके लिए अल्ट्रा-स्नाज़ी सिंटैक्स होना बेहतर है - जो कि पूर्ण-विकसित ifनहीं है। 2) "क्या आपने इसके साथ $ {निर्माण: शब्द} को भी नहीं हटाया होगा" - परिदृश्य यह है कि मैंने चर के एक प्रयोग को याद किया। उपयोग ${v:?w}करने से मुझे नुकसान से बचाया जा सकता है। अगर मैंने सभी उपयोगों को हटा दिया होता, तो सादा पहुंच हानिकारक नहीं होती, जाहिर है।
राफेल

उस सभी ने कहा, आपका जवाब सामान्य शीर्षक प्रश्न के लिए एक उचित और सहायक प्रतिक्रिया है: यह सुनिश्चित करना कि जो स्क्रिप्ट आसपास रहती हैं, उनके लिए यह सुनिश्चित करना महत्वपूर्ण है। प्रश्न निकाय में विशिष्ट मुद्दा, कुल्होदानंद द्वारा बेहतर उत्तर दिया गया है। धन्यवाद!
राफेल

4

आपके विशिष्ट मामले में, मैंने फ़ाइलों / निर्देशिकाओं को स्थानांतरित करने के लिए अतीत में 'विलोपन' को फिर से काम किया है (मान लें / tmp आपके निर्देशिका के समान विभाजन पर है):

# mktemp -d is also a good, reliable choice
trashdir=/tmp/.trash-$USER/trash-`date`
mkdir -p "$trashdir"
...
mv "${build}"/* "$trashdir"
...

पर्दे के पीछे, यह $trashdirएक ही विभाजन पर स्रोत से गंतव्य निर्देशिका संरचनाओं के लिए टॉपवेल फ़ाइल / डीआईआर संदर्भों को स्थानांतरित करता है, और निर्देशिका संरचना को चलने में समय खर्च नहीं करता है और प्रति-फ़ाइल डिस्क ब्लॉक को तब और वहीं मुक्त करता है। यह बहुत तेजी से सफाई पैदा करता है, जबकि सिस्टम सक्रिय उपयोग में है, बदले में थोड़ा धीमा रिबूट (/ रिबूट पर टैम्प को साफ किया जाता है)।

वैकल्पिक रूप से, समय-समय पर स्वच्छ /tmp/.trash-$USER के लिए एक क्रॉन प्रविष्टि, भरने के लिए / tmp को प्रक्रियाओं (जैसे, बनाता है) के लिए रखेगी जो बहुत सारे डिस्क स्थान का उपभोग करती है। यदि आपकी निर्देशिका एक अलग विभाजन / tmp के रूप में है, तो आप अपने विभाजन पर एक समान / tmp जैसी निर्देशिका बना सकते हैं और इसके बजाय क्रोन को साफ कर सकते हैं।

सबसे महत्वपूर्ण बात, हालांकि, यदि आप चर को किसी भी तरह से पेंच करते हैं, तो आप क्लीनअप होने से पहले सामग्री को पुनर्प्राप्त कर सकते हैं।


2
"यह बहुत तेजी से सफाई पैदा करता है, जबकि सिस्टम सक्रिय उपयोग में है" - क्या यह? मैंने सोचा होगा कि दोनों केवल प्रभावित इनोड को बदलने के बारे में हैं। यह निश्चित रूप से तेज़ नहीं है यदि /tmpकिसी अन्य विभाजन पर है (मुझे लगता है कि यह हमेशा मेरे लिए है, कम से कम स्क्रिप्ट के लिए जो उपयोगकर्ता अंतरिक्ष में चलते हैं); फिर, ट्रैश फ़ोल्डर को बदलना होगा (और ओएस-हैंडलिंग से लाभ नहीं होगा /tmp)।
राफेल

1
आप mktemp -dकिसी अन्य प्रक्रिया के पैर की उंगलियों पर चलने के बिना (और सही ढंग से सम्मान के लिए $TMPDIR) उस अस्थायी निर्देशिका को प्राप्त करने के लिए उपयोग कर सकते हैं ।
टोबे स्पाइट

आप दोनों से अपने सटीक और उपयोगी नोट्स जोड़े, धन्यवाद। मुझे लगता है कि मैंने मूल रूप से आपके द्वारा उठाए गए बिंदुओं पर विचार किए बिना इसे बहुत तेज़ी से पोस्ट किया है।
user117529

2

चर uninitialized हैं, जैसे जब चूक को असाइन करने के लिए bash paramater प्रतिस्थापन का उपयोग करें:

rm -rf $ {variable: - "/ nonexistent"}


1

मैं हमेशा अपनी बैश स्क्रिप्ट को एक लाइन के साथ शुरू करने की कोशिश करता हूं #!/bin/bash -ue

-eका अर्थ है "पहले अनारक्षित रार पर असफल ";

-u" यू ndeclared चर के पहले उपयोग पर विफल "।

महान लेख में अधिक जानकारी प्राप्त करें अनधिकृत बैश सख्त मोड का उपयोग करें (जब तक कि आप डिबगिंग न करें) । लेखक भी उपयोग करने की सलाह देता है set -o pipefail; IFS=$'\n\t'लेकिन मेरे उद्देश्यों के लिए यह ओवरकिल है।


1

यह जाँचने की सामान्य सलाह कि क्या आपका चर सेट है, इस तरह के मुद्दे को रोकने के लिए एक उपयोगी उपकरण है। लेकिन इस मामले में एक सरल समाधान था।

$buildनिर्देशिका की सामग्रियों को नष्ट करने की कोई आवश्यकता नहीं है, बल्कि उन्हें हटाने के लिए वास्तविक $buildनिर्देशिका की आवश्यकता नहीं है। इसलिए यदि आप बहिर्मुखी को छोड़ देते हैं *तो एक अनसेट वैल्यू बदल जाएगा, rm -rf /जो पिछले दशक में डिफ़ॉल्ट रूप से सबसे अधिक आरएम कार्यान्वयन द्वारा प्रदर्शन करने से इंकार कर देगा (जब तक कि आप जीएनयू आरएम के --no-preserve-rootविकल्प के साथ इस सुरक्षा को अक्षम नहीं करते हैं )।

अनुगामी /को छोड़ देने के rm ''परिणामस्वरूप त्रुटि संदेश प्राप्त होगा:

rm: can't remove '': No such file or directory

यह तब भी काम करता है जब आपकी rm कमांड सुरक्षा के लिए लागू नहीं होती है /


-2

आप प्रोग्रामिंग लैंग्वेज के संदर्भ में सोच रहे हैं, लेकिन बैश एक स्क्रिप्टिंग भाषा है :-) इसलिए, पूरी तरह से अलग भाषा के प्रतिमान के लिए एक पूर्ण भिन्न निर्देश प्रतिमान का उपयोग करें ।

इस मामले में:

rmdir ${build}

चूंकि rmdirएक गैर-खाली निर्देशिका को हटाने से इनकार कर दिया जाएगा, इसलिए आपको पहले सदस्यों को हटाने की आवश्यकता होगी। आप जानते हैं कि वे सदस्य क्या हैं? वे शायद आपकी स्क्रिप्ट के पैरामीटर हैं, या किसी पैरामीटर से प्राप्त हुए हैं, इसलिए:

rm -rf ${build}/${parameter}
rmdir ${build}

अब, यदि आप कुछ अन्य फाइल या निर्देशिकाओं को वहां डालते हैं जैसे कि एक अस्थायी फाइल या कुछ ऐसा जो आपको नहीं चाहिए, तो rmdirत्रुटि हो जाएगी। इसे ठीक से संभालें, फिर:

rmdir ${build} || build_dir_not_empty "${build}"

यह सोचने का तरीका मुझे अच्छी तरह से परोसा गया है ... हाँ, वहाँ किया गया है, कि किया।


6
आपके प्रयास के लिए धन्यवाद, लेकिन यह पूरी तरह से निशान से दूर है। यह धारणा "आप जानते हैं कि वे सदस्य क्या हैं" गलत है; संकलक उत्पादन के बारे में सोचो। make cleanकुछ वाइल्डकार्ड का उपयोग करेंगे (जब तक कि बहुत मेहनती कंपाइलर हर फ़ाइल की एक सूची नहीं बनाता है)। इसके अलावा, यह मुझे लगता है कि rm -rf ${build}/${parameter}केवल इस मुद्दे को थोड़ा नीचे की ओर ले जाता है।
राफेल

हम्म। देखो कि कैसे पढ़ता है। "धन्यवाद, लेकिन यह ऐसी चीज के लिए है जिसका मैंने मूल प्रश्न में खुलासा नहीं किया था इसलिए मैं न केवल आपके उत्तर को अस्वीकार करने जा रहा हूं, बल्कि इसे सामान्य मामले में अधिक लागू होने के बावजूद इसे अस्वीकार कर दूंगा।" वाह।
रिच

"मुझे प्रश्न में जो लिखा गया है उससे परे अतिरिक्त धारणाएं बनाने दें और फिर एक विशेष उत्तर लिखें।" * श्रग * (FYI करें, ऐसा नहीं है कि यह आपके किसी व्यवसाय का है: मैंने इसे कम नहीं किया। संभवतः मुझे होना चाहिए, क्योंकि उत्तर उपयोगी नहीं था (मेरे लिए, कम से कम)।)
राफेल

हम्म। मैंने कोई धारणा नहीं बनाई, और एक सामान्य उत्तर लिखा । makeसवाल में कुछ भी नहीं है । एक उत्तर लिखना जो केवल लागू makeहोता है, एक बहुत ही विशिष्ट और आश्चर्यजनक धारणा होगी। मेरे उत्तर makeसॉफ़्टवेयर विकास के अलावा अन्य मामलों के लिए काम करते हैं , आपकी विशिष्ट नौसिखिया समस्या के अलावा जो आप अपना सामान हटाकर कर रहे थे।
रिच

1
कुसलानंद ने मुझे मछली मारने का तरीका सिखाया। आप साथ आए और कहा, "चूंकि आप मैदानों में रहते हैं, आप इसके बजाय बीफ़ क्यों नहीं खाते?"
राफेल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.