पथ स्वतंत्र शेबांग


20

मेरे पास एक स्क्रिप्ट है जिसे मैं दो मशीनों में चलाना चाहता हूं। इन दो मशीनों को एक ही गिट रिपॉजिटरी से स्क्रिप्ट की प्रतियां मिलती हैं। स्क्रिप्ट को सही दुभाषिया (जैसे zsh) के साथ चलाने की आवश्यकता है ।

दुर्भाग्य से, दोनों env और zshस्थानीय और दूरस्थ मशीनों में विभिन्न स्थानों में रहते हैं:

रिमोट मशीन

$ which env
/bin/env

$ which zsh
/some/long/path/to/the/right/zsh

स्थानीय मशीन

$ which env
/usr/bin/env

$which zsh
/usr/local/bin/zsh

मैं शेबग कैसे सेट कर सकता हूं ताकि स्क्रिप्ट चलाना /path/to/script.shहमेशा Zshउपलब्ध में उपयोग होता है PATH?


8
क्या आप सुनिश्चित हैं कि envदोनों / बिन और / usr / बिन में नहीं हैं? which -a envपुष्टि करने का प्रयास करें।
ग्रिटिटी

जवाबों:


22

आप इसे सीधे शबंग के माध्यम से हल नहीं कर सकते, क्योंकि शबंग पूरी तरह से स्थिर है। आप क्या कर सकते हैं कुछ »कम से कम आम गुणक« (एक शेल परिप्रेक्ष्य से) शेलबैंग में और अपने स्क्रिप्ट को सही शेल के साथ फिर से निष्पादित करते हैं, अगर यह एलसीएम जेडश नहीं है। दूसरे शब्दों में: क्या आपकी स्क्रिप्ट को सभी प्रणालियों पर पाए गए शेल द्वारा निष्पादित किया गया है, एक zsh-फीचर के लिए परीक्षण करें और यदि परीक्षण गलत निकला, तो उस स्क्रिप्ट के execसाथ है zsh, जहां परीक्षण सफल होगा और आप जारी रखेंगे।

एक अनूठी विशेषता zsh, उदाहरण के लिए, $ZSH_VERSIONचर की उपस्थिति है :

#!/bin/sh -

[ -z "$ZSH_VERSION" ] && exec zsh - "$0" ${1+"$@"}

# zsh-specific stuff following here
echo "$ZSH_VERSION"

इस सरल मामले में, स्क्रिप्ट को सबसे पहले निष्पादित किया जाता है /bin/sh(सभी 80 के दशक के बाद यूनिक्स की तरह सिस्टम को समझते हैं #!और एक है /bin/sh, या तो बॉर्न या पोसिक्स लेकिन हमारा सिंटैक्स दोनों के लिए संगत है)। तो $ZSH_VERSIONहै नहीं निर्धारित करते हैं, स्क्रिप्ट exec'ही के माध्यम से है zsh। यदि $ZSH_VERSIONसेट किया गया है (सम्मान; स्क्रिप्ट पहले से ही चल रही है zsh), परीक्षण बस छोड़ दिया गया है। Voilà।

यह केवल तभी विफल होता है जब zshवह बिल्कुल नहीं हो $PATH

संपादित करें: यह सुनिश्चित करने के लिए, आप सामान्य स्थानों में केवल execएक zsh, आप कुछ का उपयोग कर सकते हैं

for sh in /bin/zsh \
          /usr/bin/zsh \
          /usr/local/bin/zsh; do
    [ -x "$sh" ] && exec "$sh" - "$0" ${1+"$@"}
done

यह आपको गलती से execआपके पास कुछ ऐसा करने से बचा सकता $PATHहै जिसकी zshआप उम्मीद नहीं कर रहे हैं।


मैं भव्यता के लिए इस upvoted लेकिन यह करता है, सिद्धांत रूप में, सुरक्षा / संगतता के मुद्दों है, अगर पहले zshमें $PATHएक जैसा कि आप उम्मीद नहीं है।
रेयान रीच

इसे संबोधित करने की कोशिश की। प्रश्न यह है कि क्या आप हमेशा सुनिश्चित कर सकते हैं कि क्या zshमानक स्थानों में एक द्विआधारी वास्तव में एक है zsh
एंड्रियास विसे 5

आप गतिशील रूप से बैंग लाइन को पथ कर सकते हैं। आप खुद से यह भी पूछ सकते हैं कि zshवह कहां है zsh -c 'whence zsh'। अधिक बस आप कर सकते हैं command -v zsh। कैसे गतिशील रूप से पथ के लिए मेरा जवाब देखें #!bang
मिकसेर्व

1
zshबाइनरी को कॉल करने से बाइनरी $PATHका रास्ता zshकाफी समस्या को संबोधित नहीं करेगा @RyanReich ने कहा, क्या यह होगा? :)
एंड्रियास विसे

नहीं अगर आप zshखुद को निष्पादित करते हैं, नहीं, मुझे नहीं लगता है। लेकिन अगर आप अपने हैश बैंग में परिणामी स्ट्रिंग को एम्बेड करते हैं और फिर अपनी खुद की स्क्रिप्ट निष्पादित करते हैं तो आप कम से कम जानते हैं कि आपको क्या मिल रहा है। फिर भी, यह इसे पाटने की तुलना में सरल परीक्षण के लिए बनायेगा।
मिकसेर्व

7

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

बैश / Zsh / आदि।

#!/bin/sh

# Determines which OS and then reruns this script with approp. shell interp.
LIN_BASH="/bin/sh";
SOL_BASH="/packages/utilities/bin/sun5/bash";

OS_TYPE=`uname -s`;

if [ $OS_TYPE = "SunOS" ]; then
  $SOL_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
elif [ $OS_TYPE = "Linux" ]; then
  $LIN_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
else
  echo "UNKNOWN OS_TYPE, $OS_TYPE";
  exit 1;
fi
exit 0;

### BEGIN

...script goes here...

उपरोक्त व्याख्याकारों की एक किस्म के लिए आसानी से अनुकूलित किया जा सकता है। मुख्य टुकड़ा यह है कि यह स्क्रिप्ट शुरू में बॉर्न शेल के रूप में चलती है। यह फिर से अपने आप को दूसरी बार कॉल करता है, लेकिन ### BEGINउपयोग करने वाले टिप्पणी के ऊपर सब कुछ पार्स करता है sed

पर्ल

यहाँ पर्ल के लिए एक समान चाल है:

#!/bin/sh

LIN_PERL="/usr/bin/perl";
SOL_PERL="/packages/perl/bin/perl";

OS_TYPE=`uname -s`;

if [ $OS_TYPE = "SunOS" ]; then
  eval 'exec $SOL_PERL -x -S $0 ${1+"$@"}';
elif [ $OS_TYPE = "Linux" ]; then
  eval 'exec $LIN_PERL -x -S $0 ${1+"$@"}';
else
  echo "$OS_TYPE: UNSUPORRTED OS/PLATFORM";
  exit 0;
fi
exit 0;

#!perl

...perl script goes here...

यह विधि पर्ल की क्षमता का उपयोग करती है जब चलाने के लिए एक फ़ाइल दी जाती है तो कहा जाएगा कि फ़ाइल लाइन से पहले की सभी लाइनों को लंघन कर रही है #! perl


मुद्दों की एक संख्या: लापता उद्धरण, के $*बजाय का "$@"उपयोग, eval का बेकार उपयोग, बाहर निकलने की स्थिति की सूचना नहीं दी (आप execपहले एक के लिए इस्तेमाल नहीं किया ), लापता -/ --, त्रुटि संदेश stderr पर नहीं, त्रुटि की स्थिति के लिए 0 से बाहर निकलने की स्थिति , LIN_BASH के लिए / बिन / श, बेकार सेमीकोलन (कॉस्मेटिक), गैर-एनवी चर के लिए सभी अपरकेस का उपयोग कर। uname -sकी तरह है uname(Unix यूनिक्स नाम के लिए है)। आप यह उल्लेख करना भूल गए कि स्किप-x करने से विकल्प चालू हो जाता है perl
स्टीफन चेज़लस

4

नोट: @ jw013 नीचे टिप्पणी में निम्नलिखित असमर्थित आपत्ति बनाता है :

डाउनवोट इसलिए है क्योंकि आमतौर पर स्व-संशोधित कोड को बुरा व्यवहार माना जाता है। छोटे विधानसभा कार्यक्रमों के पुराने दिनों में यह सशर्त शाखाओं को कम करने और प्रदर्शन में सुधार करने का एक चतुर तरीका था, लेकिन आजकल सुरक्षा जोखिमों से अधिक लाभ मिलता है। यदि स्क्रिप्ट चलाने वाले उपयोगकर्ता ने स्क्रिप्ट पर विशेषाधिकार नहीं लिखे हैं तो आपका दृष्टिकोण काम नहीं करेगा।

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

मैं शेबांग कैसे सेट कर सकता हूं ताकि स्क्रिप्ट को /path/to/script.sh के रूप में चला रहा हो, वह हमेशा PATH में उपलब्ध Zsh का उपयोग करता है?

संतुष्ट नहीं, @ jw013 ने कम से कम कुछ गलत बयानों के साथ अभी तक असमर्थित तर्क को आगे बढ़ाते हुए आपत्ति जारी रखी :

आप एक फाइल का उपयोग करते हैं, दो फाइलों का नहीं। [ man shसंदर्भित] पैकेज एक फ़ाइल किसी अन्य फ़ाइल को संशोधित किया है। आपके पास एक फाइल ही है। इन दोनों मामलों में एक अलग अंतर है। एक फाइल जो इनपुट लेती है और आउटपुट उत्पन्न करती है वह ठीक है। एक निष्पादन योग्य फ़ाइल जो अपने आप को बदल देती है क्योंकि यह आमतौर पर एक बुरा विचार है। आपने जो उदाहरण दिया वह ऐसा नहीं करता है।

पहली जगह में:

केवल निष्पादन योग्य किसी में कोड निष्पादन योग्य खोल स्क्रिप्ट है #!खुद को

(हालांकि भी #!है आधिकारिक तौर पर अनिर्दिष्ट )

{   cat >|./file 
    chmod +x ./file 
    ./file
} <<-\FILE
    #!/usr/bin/sh
    {   ${l=lsof -p} $$
        echo "$l \$$" | sh
    } | grep \
        "COMMAND\|^..*sh\| [0-9]*[wru] "
#END
FILE

##OUTPUT

COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
file    8900 mikeserv  txt    REG   0,33   774976  2148676 /usr/bin/bash
file    8900 mikeserv  mem    REG   0,30           2148676 /usr/bin/bash (path dev=0,33)
file    8900 mikeserv    0r   REG   0,35      108 15496912 /tmp/zshUTTARQ (deleted)
file    8900 mikeserv    1u   CHR  136,2      0t0        5 /dev/pts/2
file    8900 mikeserv    2u   CHR  136,2      0t0        5 /dev/pts/2
file    8900 mikeserv  255r   REG   0,33      108  2134129 /home/mikeserv/file
COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
sh      8906 mikeserv  txt    REG   0,33   774976  2148676 /usr/bin/bash
sh      8906 mikeserv  mem    REG   0,30           2148676 /usr/bin/bash (path dev=0,33)
sh      8906 mikeserv    0r  FIFO    0,8      0t0 15500515 pipe
sh      8906 mikeserv    1w  FIFO    0,8      0t0 15500514 pipe
sh      8906 mikeserv    2u   CHR  136,2      0t0        5 /dev/pts/2

{    sed -i \
         '1c#!/home/mikeserv/file' ./file 
     ./file 
     sh -c './file ; echo'
     grep '#!' ./file
}

##OUTPUT
zsh: too many levels of symbolic links: ./file
sh: ./file: /home/mikeserv/file: bad interpreter: Too many levels of symbolic links

#!/home/mikeserv/file

एक शेल स्क्रिप्ट सिर्फ एक पाठ फ़ाइल है - इसके लिए किसी भी प्रभाव के लिए इसे किसी अन्य निष्पादन योग्य फ़ाइल द्वारा पढ़ा जाना चाहिए , इसके निर्देशों को फिर उस अन्य निष्पादन योग्य फ़ाइल द्वारा व्याख्या की जाती है, इससे पहले कि अन्य निष्पादन योग्य फ़ाइल फिर उसकी व्याख्या को निष्पादित करती है। शेल स्क्रिप्ट। यह है संभव नहीं एक खोल स्क्रिप्ट फ़ाइल के निष्पादन के लिए के लिए कम से कम दो फ़ाइलों शामिल है। zshअपने स्वयं के संकलक में एक संभावित अपवाद है , लेकिन इसके साथ मुझे बहुत कम अनुभव है और यह किसी भी तरह से यहां का प्रतिनिधित्व नहीं करता है।

एक शेल स्क्रिप्ट का हैशबैंग अपने इच्छित दुभाषिया को इंगित करना चाहिए या अप्रासंगिक के रूप में त्याग दिया जाना चाहिए।

शेल के टोकन मान्यता / निष्पादन व्यवहार मानकों पर परिभाषित किया गया है

शेल के पास अपने इनपुट को पार्स करने और व्याख्या करने के दो बुनियादी तरीके हैं: या तो इसका वर्तमान इनपुट परिभाषित <<here_documentकर रहा है या इसे परिभाषित कर रहा है { ( command |&&|| list ) ; } &- दूसरे शब्दों में, शेल या तो एक टोकन को एक कमांड के लिए एक सीमांकक के रूप में व्याख्या करता है जिसे इसे पढ़ने के बाद इसे निष्पादित करना चाहिए। या एक फ़ाइल बनाने के निर्देश के रूप में और दूसरे कमांड के लिए फाइल डिस्क्रिप्टर में मैप करें। बस।

जब आरक्षित शब्दों के सेट पर शेल परिसीमन टोकन को निष्पादित करने के लिए कमांड की व्याख्या करता है जब लागू - - या बंद करने की तरह टोकन खोल एक उद्घाटन टोकन यह इस तरह एक नई पंक्ति के रूप में एक आदेश सूची में पढ़ने के लिए जारी रखना चाहिए जब तक सूची या तो टोकन एक बंद से सीमांकित किया जाता है का सामना करना पड़ता है जब })के लिए ({क्रियान्वयन से पहले।

शेल एक साधारण कमांड और एक कंपाउंड कमांड के बीच अंतर करता है यौगिक आदेश आदेशों क्रियान्वयन से पहले में पढ़ा जाना चाहिए का सेट है, लेकिन खोल प्रदर्शन नहीं करता $expansionउसके घटक में से किसी पर सरल आदेशों जब तक यह अकेले हर एक निष्पादित करता है।

इसलिए, निम्नलिखित उदाहरण में, ;semicolon आरक्षित शब्द व्यक्तिगत सरल आदेशों का परिसीमन करते हैं जबकि गैर-बचा हुआ \newlineचरित्र दो यौगिक आदेशों के बीच परिसीमन करता है :

{   cat >|./file
    chmod +x ./file
    ./file
} <<-\FILE
        #!/usr/bin/sh
        echo "simple command ${sc=1}" ;\
                : > $0 ;\
                echo "simple command $((sc+2))" ;\
                sh -c "./file && echo hooray"
        sh -c "./file && echo hooray"
#END
FILE

##OUTPUT

simple command 1
simple command 3
hooray

यह दिशानिर्देशों का सरलीकरण है। यह बहुत अधिक जटिल हो जाता है जब आप शेल-बिल्डिंस, सबहेल, वर्तमान वातावरण और आदि पर विचार करते हैं, लेकिन, यहां मेरे उद्देश्यों के लिए, यह पर्याप्त है।

और की बात कर बनाया-इन और आदेश-सूचियों, एक function() { declaration ; }महज एक बताए का एक साधन है यौगिक आदेश एक करने के लिए साधारण आदेश। शेल को $expansionsघोषणा के बयान पर कोई भी प्रदर्शन नहीं करना चाहिए - शामिल करने के लिए <<redirections>- लेकिन इसके बजाय परिभाषा को एक एकल, शाब्दिक स्ट्रिंग के रूप में संग्रहीत करना चाहिए और इसे एक विशेष शेल के रूप में निष्पादित करना चाहिए जब इसे बुलाया जाता है।

इसलिए एक निष्पादन योग्य शेल स्क्रिप्ट में घोषित एक शेल फ़ंक्शन को अपने शाब्दिक स्ट्रिंग रूप में शेल की मेमोरी की व्याख्या करने में संग्रहीत किया जाता है - इनपुट के रूप में यहां संलग्न दस्तावेजों को सम्मिलित करने के लिए अनएक्सपेन्डेड - और इसके स्रोत फ़ाइल के स्वतंत्र रूप से निष्पादित होने पर हर बार इसे शेल शेल के रूप में कहा जाता है- जब तक शेल का वर्तमान वातावरण रहता है, तब तक।

एक ऑनलाइन <<HERE-DOCUMENTफाइल है

पुनर्निर्देशन ऑपरेटर <<और <<-दोनों एक कमांड के इनपुट के लिए एक शेल इनपुट फ़ाइल में निहित लाइनों के पुनर्निर्देशन की अनुमति देते हैं, जिसे यहां दस्तावेज़ के रूप में जाना जाता है ।

यहां-दस्तावेज़ एक शब्द है कि अगले बाद शुरू होता है के रूप में माना जाएगा \newlineऔर जब तक वहाँ केवल रखने वाली पंक्ति है जारी है सीमांकक और एक \newline, कोई साथ [:blank:]के बीच में है। फिर अगला यहाँ-दस्तावेज़ शुरू होता है, अगर वहाँ एक है। प्रारूप इस प्रकार है:

[n]<<word
    here-document 
delimiter

... जहां वैकल्पिक nफ़ाइल डिस्क्रिप्टर नंबर का प्रतिनिधित्व करता है। यदि संख्या छोड़ दी जाती है, तो यहां दस्तावेज़ मानक इनपुट (फाइल डिस्क्रिप्टर 0) को संदर्भित करता है ।

for shell in dash zsh bash sh ; do sudo $shell -c '
        {   readlink /proc/self/fd/3
            cat <&3
        } 3<<-FILE
            $0

        FILE
' ; done

#OUTPUT

pipe:[16582351]
dash

/tmp/zshqs0lKX (deleted)
zsh

/tmp/sh-thd-955082504 (deleted)
bash

/tmp/sh-thd-955082612 (deleted)
sh

आप समझ सकते हैं? शेल के ऊपर प्रत्येक शेल के लिए एक फाइल बनाता है और इसे फाइल डिस्क्रिप्टर में मैप करता है। में zsh, (ba)shखोल में एक नियमित रूप से फ़ाइल बनाता है /tmp, उत्पादन उदासीनता किसी वर्णनकर्ता करने के लिए इसे नक्शे, तो हटाता /tmpफ़ाइल इसलिए वर्णनकर्ता की गिरी के प्रति सब अवशेष है। dashउस बकवास के सभी से बचा जाता है और बस अपने आउटपुट प्रोसेसिंग को एक अनाम |pipeफाइल में रीडायरेक्ट <<लक्ष्य पर लक्षित करता है।

यह बनाता है dash:

cmd <<HEREDOC
    $(cmd)
HEREDOC

कार्यात्मक रूप से इसके समकक्ष bash:

cmd <(cmd)

जबकि dashकार्यान्वयन कम से कम POSIXly पोर्टेबल है।

जिससे कई फ़ाइलें

इसलिए नीचे दिए गए उत्तर में जब मैं करता हूं:

{    cat >|./file
     chmod +x ./file
     ./file
} <<\FILE
#!/usr/bin/sh
_fn() { printf '#!' ; command -v zsh ; cat 
} <<SCRIPT >$0
    [SCRIPT BODY]
SCRIPT    

_fn ; exec $0
FILE

निम्नलिखित होता है:

  1. मैं पहली बार catके लिए जो भी फ़ाइल खोल के लिए बनाई गई सामग्री FILEमें ./file, यह निष्पादन योग्य बनाने, तो यह निष्पादित।

  2. कर्नेल ने एक फाइल डिस्क्रिप्टर को सौंपा #!और उसके /usr/bin/shसाथ कॉल करता है ।<read ./file

  3. shस्मृति में एक स्ट्रिंग है जो कंपाउंड कमांड से शुरू _fn()और अंत में सम्‍मिलित है SCRIPT

  4. जब _fnकहा जाता है, shपहले तो एक विवरणक के लिए फाइल में परिभाषित नक्शा व्याख्या करना चाहिए <<SCRIPT...SCRIPT से पहले लागू _fnनिर्मित उपयोगिता एक विशेष रूप क्योंकि SCRIPTहै _fnकी<input.

  5. इसके द्वारा किए गए तार आउटपुट printfऔर 's' के मानक-command आउट के लिए लिखे गए हैं, जिन्हें वर्तमान शेल - या में पुनर्निर्देशित किया गया है ।_fn >&1ARGV0$0

  6. catअपने <&0 मानक-इनपुट फ़ाइल-डिस्क्रिप्टर - SCRIPTको >काटे गए वर्तमान शेल के ARGV0तर्क पर, या $0

  7. अपने पहले से ही पढ़े- लिखे वर्तमान कंपाउंड कमांड में , sh execनिष्पादन योग्य - और नए पुनर्लेखन - $0तर्क को पूरा करना।

समय ./fileतक अपने निहित निर्देश निर्दिष्ट है कि यह किया जाना चाहिए कहा जाता है execफिर d, shएक एकल में इसे पढ़ता यौगिक आदेश एक समय में के रूप में यह उन्हें कार्यान्वित करता है, जबकि ./fileखुद को बिल्कुल भी कुछ नहीं करता है खुशी से अपनी नई सामग्री स्वीकार छोड़कर। फ़ाइलें जो वास्तव में काम पर हैं/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.

धन्यवाद, सभी के बाद

तो जब @ jw013 निर्दिष्ट करता है कि:

एक फाइल जो इनपुट लेती है और आउटपुट उत्पन्न करती है, ठीक है ...

... इस जवाब की अपनी गलत आलोचना के बीच, वह वास्तव में अनजाने में यहां इस्तेमाल की जाने वाली एकमात्र विधि की निंदा कर रहा है, जो मूल रूप से सिर्फ काम करता है:

cat <new_file >old_file

उत्तर

यहां सभी उत्तर अच्छे हैं, लेकिन उनमें से कोई भी पूरी तरह से सही नहीं है। हर कोई दावा करता है कि आप गतिशील और स्थायी रूप से अपना रास्ता नहीं बना सकते #!bang। यहां एक पथ स्वतंत्र शेबांग की स्थापना का प्रदर्शन है:

डेमो

{   cat >|./file
    chmod +x ./file
    ./file
} <<\FILE 
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
        ${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
        printf "
        \$0    :\t$0
        lines :\t$((c=$(wc -l <$0)))
        !bang :\t$(sed 1q "$0")
        shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
        sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
                sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me ; exec $0
FILE

आउटपुट

        $0    : ./file
        lines : 13
        !bang : #!/usr/bin/sh
        shell : /usr/bin/sh

1 >     #!/usr/bin/sh
2 >     _rewrite_me() { printf '#!' ; command -v zsh
...
12 >    SCRIPT
13 >    _rewrite_me ; out=$0 _rewrite_me ; exec $0

        $0    : /home/mikeserv/file
        lines : 8
        !bang : #!/usr/bin/zsh
        shell : /usr/bin/zsh

1 >     #!/usr/bin/zsh
2 >             printf "
...
7 >             sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
8 >                     sed -e 'N;s/\n/ >\t/' -e 4a\\...

आप समझ सकते हैं? हम सिर्फ स्क्रिप्ट को ओवरराइट करते हैं। और यह केवल एक बार gitसिंक के बाद ही होता है । उस बिंदु से इसे # बैंग लाइन में सही रास्ता मिल गया है।

अब लगभग सभी वहाँ बस फुलाना है। इसे सुरक्षित रूप से करने के लिए आपको आवश्यकता है:

  1. एक फ़ंक्शन शीर्ष पर परिभाषित किया गया और नीचे लिखा गया है जो लेखन करता है। इस तरह हम अपनी जरूरत की हर चीज को मेमोरी में स्टोर कर लेते हैं और पूरी फाइल पढ़ने से पहले यह सुनिश्चित कर लेते हैं कि हम इस पर लिखना शुरू कर दें।

  2. यह निर्धारित करने का कोई तरीका कि रास्ता क्या होना चाहिए। command -vउसके लिए बहुत अच्छा है।

  3. Heredocs वास्तव में मदद करते हैं क्योंकि वे वास्तविक फाइलें हैं। वे इस बीच आपकी स्क्रिप्ट को संग्रहीत करेंगे। आप तार का उपयोग कर सकते हैं लेकिन ...

  4. आपको यह सुनिश्चित करना होगा कि शेल कमांड में पढ़ता है जो आपकी स्क्रिप्ट को उसी कमांड सूची में लिखता है जो इसे निष्पादित करता है।

देखो:

{   cat >|./file
    chmod +x ./file
    ./file
} <<\FILE 
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
        ${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
        printf "
        \$0    :\t$0
        lines :\t$((c=$(wc -l <$0)))
        !bang :\t$(sed 1q "$0")
        shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
        sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
                sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me
exec $0
FILE

ध्यान दें कि मैंने केवल execकमांड को एक पंक्ति में स्थानांतरित किया है । अभी:

#OUTPUT
        $0    : ./file
        lines : 14
        !bang : #!/usr/bin/sh
        shell : /usr/bin/sh

1 >     #!/usr/bin/sh
2 >     _rewrite_me() { printf '#!' ; command -v zsh
...
13 >    _rewrite_me ; out=$0 _rewrite_me
14 >    exec $0

मुझे आउटपुट का दूसरा भाग नहीं मिलता क्योंकि स्क्रिप्ट अगले कमांड में नहीं पढ़ सकती। फिर भी, क्योंकि एकमात्र कमांड गायब था अंतिम था:

cat ./file

#!/usr/bin/zsh
        printf "
        \$0    :\t$0
        lines :\t$((c=$(wc -l <$0)))
        !bang :\t$(sed 1q "$0")
        shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
        sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
                sed -e 'N;s/\n/ >\t/' -e 4a\\...

स्क्रिप्ट के माध्यम से आया था जैसा कि होना चाहिए - ज्यादातर क्योंकि यह सब हेरेडोक में था - लेकिन अगर आप इसे सही तरीके से योजना नहीं बनाते हैं, तो आप अपनी फाइलस्ट्रीम को छोटा कर सकते हैं, जो कि मेरे ऊपर हुआ है।


डाउनवोट इसलिए है क्योंकि आमतौर पर स्व-संशोधित कोड को बुरा व्यवहार माना जाता है। छोटे विधानसभा कार्यक्रमों के पुराने दिनों में यह सशर्त शाखाओं को कम करने और प्रदर्शन में सुधार करने का एक चतुर तरीका था, लेकिन आजकल सुरक्षा जोखिमों से अधिक लाभ मिलता है। यदि स्क्रिप्ट चलाने वाले उपयोगकर्ता के पास स्क्रिप्ट पर विशेषाधिकार नहीं हैं, तो आपका दृष्टिकोण काम नहीं करेगा।
15:013 बजे jw013

@ jw013 स्पष्ट रूप से एक निष्पादन योग्य स्क्रिप्ट को स्थापित करने या अद्यतन करने के लिए मेरा दृष्टिकोण काम नहीं करेगा यदि स्क्रिप्ट को स्थापित करने या अपडेट करने का प्रयास करने वाले व्यक्ति के पास स्क्रिप्ट को स्थापित या अपडेट करने की अनुमति नहीं थी वास्तव में, यह विशेष रूप से है जो इस उत्तर को यहां हर दूसरे उत्तर से बेहतर बनाता है - यह आवश्यक के रूप में एक सटीक # बैंग लाइन प्रदान कर सकता है और स्थापना के दौरान केवल पहले आह्वान पर ऐसा करने के लिए किसी विशेष अनुमति की आवश्यकता होती है और, फिर से, मैं केवल इसके लिए आपका शब्द नहीं लूंगा कि आत्म संशोधन कोड बुरा अभ्यास है - कृपया man commandएक विरोधाभासी राय देखें।
mikeserv

man commandएक विरोधाभासी राय के लिए कृपया देखें - एक नहीं मिल रहा है। क्या आप मुझे उस विशिष्ट अनुभाग / पैराग्राफ तक निर्देशित कर सकते हैं जिसके बारे में आप बात कर रहे थे?
jw013

@ jw013 - मेरी गलती, क्या है man shके लिए खोज - 'कमांड -v'। मुझे पता था कि यह उन manपृष्ठों में से एक था जो मैं दूसरे दिन देख रहा था।
mikeserv

मुझे लगता है कि यह वह command -vउदाहरण है जिसके बारे में आप बात कर रहे थे man sh। यह सामान्य दिखने वाला इंस्टॉलर स्क्रिप्ट है, न कि सेल्फ-मॉडिफ़ाइड । यहां तक ​​कि स्व-निहित इंस्टॉलर में केवल पूर्व-संशोधन इनपुट होते हैं, और उनके संशोधनों को कहीं और आउटपुट करते हैं। जिस तरह से आप सिफारिश कर रहे हैं, वे खुद को फिर से नहीं लिखते हैं।
jw013

1

यहाँ एक तरह से स्व मॉडिफाइड स्क्रिप्ट है जो इसके शेबंग को ठीक करती है। इस कोड को आपकी वास्तविक स्क्रिप्ट के लिए तैयार किया जाना चाहिए।

#!/bin/sh
# unpatched

PATH=`PATH=/bin:/usr/bin:$PATH getconf PATH`
if [ "`awk 'NR==2 {print $2;exit;}' $0`" = unpatched ]; then
  [ -z "`PATH=\`getconf PATH\`:/usr/local/bin:/some/long/path/to/the/right:$PATH command -v zsh`" ] && { echo "zsh not found"; exit 1; }
  cp -- "$0" "$0.org" || exit 1
  mv -- "$0" "$0.old" || exit 1
  (
    echo "#!`PATH=\`getconf PATH\`:$PATH command -v zsh`" 
    sed -n '/^##/,$p' $0.old
  ) > $0 || exit
  chmod +x $0
  rm $0.old
  sync
  exit
fi
## Original script starts here

कुछ टिप्पणियां:

  • इसे एक बार उस व्यक्ति द्वारा चलाया जाना चाहिए, जिसके पास उस स्क्रिप्ट में फ़ाइलें बनाने और हटाने का अधिकार है जहां स्क्रिप्ट स्थित है।

  • यह केवल लीगेसी बॉर्न शेल सिंटैक्स का उपयोग करता है, /bin/shजैसा कि लोकप्रिय विश्वास के बावजूद, पोसिक्स शेल होने की गारंटी नहीं है, यहां तक ​​कि पोसिक्स अनुरूप ओएस भी।

  • इसने POSIX को एक POSIX कंप्लेंट के लिए सेट किया, जिसके बाद "zony" zsh लेने से बचने के लिए संभावित zsh स्थानों की सूची दी गई।

  • यदि किसी कारण से, एक स्व-संशोधित स्क्रिप्ट का खुलासा नहीं किया जाता है, तो एक के बजाय दो लिपियों को वितरित करना तुच्छ होगा, पहला वह जिसे आप पैच करना चाहते हैं और दूसरा, जिसे मैंने पूर्व में संसाधित करने के लिए थोड़ा संशोधित करने का सुझाव दिया था।


/bin/shबिंदु एक अच्छा एक है - लेकिन इस मामले में आप एक premodified की आवश्यकता है #!सब पर? और नहीं है awkबस के रूप में होने की संभावना जाली के रूप में zshहै?
mikeserv

PmIX awk को कॉल करने के लिए @mikeserv उत्तर अपडेट किया गया। गैर-बूर्न संगत शेल द्वारा व्याख्या की जाने वाली स्क्रिप्ट को रोकने के लिए प्रीमियर शेबंग है, यह आपका लॉगिन शेल होना चाहिए।
जलीगेरे

समझ में आता है। मैंने इसे उत्कीर्ण किया क्योंकि यह काम करता है, यह पुस्तक से चिपक जाता है और यह संभावित शेल वातावरण / फ़ाइल हैंडलिंग की ध्वनि समझ को प्रदर्शित करता है - विशेष रूप से आपके द्वारा उपयोग की जाने वाली बैकअप फाइलें, जो सभी जीएनयू sed -iवैसे भी है। मैं व्यक्तिगत $PATHरूप से एक अन्य उत्तर पर टिप्पणियों में दी गई समस्या के बारे में सोचता हूं और जिसे आप सुरक्षित रूप से संबोधित करते हैं जैसे कि मैं कुछ पंक्तियों में यहां बता सकता हूं कि बस और स्पष्ट रूप से निर्भरता और / या कठोर और स्पष्ट परीक्षण को परिभाषित करना बेहतर है - उदाहरण के लिए, अब getconfहो सकता है फोनी, लेकिन संभावना शून्य के पास है, जैसे वे के लिए थे zshऔरawk.
mikeserv

@mikeserv, स्क्रिप्ट को एक फर्जी गेटकॉफ़ कॉल करने के लिए जोखिम को कम करने के लिए संशोधित किया गया।
जलीगेरे

$(getconf PATH)बॉर्न नहीं है। cp $0 $0.oldzsh वाक्य रचना है। बॉर्न समतुल्य होगा cp "$0" "$0.old"हालांकि आप चाहते हैंcp -- "$0" "$0.old"
स्टीफन चेज़लस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.