Đường dẫn shebang độc lập


20

Tôi có một kịch bản mà tôi muốn có thể chạy trong hai máy. Hai máy này nhận được các bản sao của tập lệnh từ cùng một kho git. Kịch bản cần chạy với trình thông dịch đúng (ví dụ zsh).

Thật không may, cả hai envzshsống ở các địa điểm khác nhau trong các máy cục bộ và từ xa:

Máy từ xa

$ which env
/bin/env

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

Máy địa phương

$ which env
/usr/bin/env

$which zsh
/usr/local/bin/zsh

Làm cách nào tôi có thể thiết lập shebang để chạy tập lệnh /path/to/script.shluôn sử dụng Zshcó sẵn trong PATH?


8
Bạn có chắc chắn envkhông có trong cả / bin và / usr / bin không? Hãy thử which -a envxác nhận.
grawity

Câu trả lời:


22

Bạn không thể giải quyết điều này thông qua shebang trực tiếp, vì shebang hoàn toàn tĩnh. Những gì bạn có thể làm là có một số »số nhân ít phổ biến nhất« (từ phối cảnh shell) trong shebang và thực hiện lại tập lệnh của bạn với shell bên phải, nếu LCM này không phải là zsh. Nói cách khác: Có tập lệnh của bạn được thực thi bởi trình bao trên tất cả các hệ thống, hãy kiểm tra zshtính năng chỉ có một lần và nếu thử nghiệm biến thành sai, hãy đặt tập lệnh execvới zsh, nơi thử nghiệm sẽ thành công và bạn chỉ cần tiếp tục.

zshVí dụ, một tính năng duy nhất là sự hiện diện của $ZSH_VERSIONbiến:

#!/bin/sh -

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

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

Trong trường hợp đơn giản này, tập lệnh được thực thi đầu tiên bởi /bin/sh(tất cả các hệ thống giống như Unix sau thập niên 80 đều hiểu #!và có một /bin/shBourne hoặc POSIX nhưng cú pháp của chúng tôi tương thích với cả hai). Nếu $ZSH_VERSIONkhông được thiết lập, kịch bản execlà chính nó thông qua zsh. Nếu $ZSH_VERSIONđược đặt (tương ứng với tập lệnh đã được chạy qua zsh), bài kiểm tra chỉ đơn giản là bỏ qua. Võngà.

Điều này chỉ thất bại nếu zshkhông có gì $PATHcả.

Chỉnh sửa: Để đảm bảo, bạn chỉ ở execmột zshnơi thông thường, bạn có thể sử dụng một cái gì đó như

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

Điều này có thể cứu bạn khỏi việc vô tình exec'lấy thứ gì $PATHđó không phải là thứ zshbạn mong đợi.


Tôi upvoted này cho sang trọng nhưng nó, về nguyên tắc, có vấn đề an ninh / tương thích, nếu là người đầu tiên zshtrong $PATHkhông phải là người bạn mong đợi.
Ryan Reich

Đã cố gắng để giải quyết nó. Câu hỏi là, liệu bạn luôn có thể chắc chắn liệu một zshnhị phân trong các vị trí tiêu chuẩn thực sự là một zsh.
Andreas Wiese

Bạn có thể tự động đường dẫn dòng! Bạn cũng có thể zshtự hỏi nó ở đâu với zsh -c 'whence zsh'. Đơn giản hơn bạn có thể chỉ command -v zsh. Xem câu trả lời của tôi để biết cách linh hoạt đường dẫn #!bang.
mikeerv

1
Gọi zshnhị phân từ $PATHđể có được đường dẫn của zshnhị phân sẽ không giải quyết được vấn đề @RyanReich đã chỉ ra, phải không? :)
Andreas Wiese

Không, nếu bạn thực hiện zshchính nó, không, tôi đoán là không. Nhưng nếu bạn nhúng chuỗi kết quả vào hàm băm của bạn và sau đó thực thi tập lệnh của riêng bạn thì ít nhất bạn cũng biết bạn đang nhận được gì. Tuy nhiên, nó sẽ làm cho một bài kiểm tra đơn giản hơn là lặp nó.
mikeerv

7

Trong nhiều năm, tôi đã sử dụng một cái gì đó tương tự để đối phó với các vị trí khác nhau của Bash trên các hệ thống mà tôi cần các tập lệnh của mình để chạy.

Bash / Zsh / v.v.

#!/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...

Ở trên có thể dễ dàng thích nghi cho một loạt các thông dịch viên. Mấu chốt là kịch bản này ban đầu chạy dưới dạng Bourne shell. Sau đó, nó gọi đệ quy lần thứ hai, nhưng phân tích mọi thứ bên trên nhận xét ### BEGINbằng cách sử dụng sed.

Perl

Đây là một mẹo tương tự cho Perl:

#!/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...

Phương pháp này sử dụng khả năng của Perl khi được cung cấp một tệp để chạy sẽ phân tích tệp đã bỏ qua tất cả các dòng nằm trước dòng #! perl.


Một số vấn đề ở đó: thiếu dấu ngoặc kép, sử dụng $*thay vì "$@"sử dụng eval vô dụng, trạng thái thoát không được báo cáo (bạn không sử dụng execcho lần đầu tiên), thiếu -/ --, thông báo lỗi không có trong stderr, 0 trạng thái thoát cho các điều kiện lỗi , sử dụng / bin / sh cho LIN_BASH, dấu chấm phẩy vô dụng (mỹ phẩm), sử dụng tất cả chữ hoa cho các biến không env. uname -sgiống như uname(uname là tên Unix). Bạn đã quên đề cập đến việc bỏ qua được kích hoạt bởi -xtùy chọn perl.
Stéphane Chazelas

4

LƯU Ý: @ jw013 đưa ra phản đối không được hỗ trợ sau trong các nhận xét bên dưới:

Downvote là bởi vì mã tự sửa đổi thường được coi là thực hành xấu. Quay lại thời kỳ cũ của các chương trình lắp ráp nhỏ, đó là một cách thông minh để giảm các nhánh có điều kiện và cải thiện hiệu suất, nhưng ngày nay các rủi ro bảo mật vượt xa các lợi thế. Cách tiếp cận của bạn sẽ không hoạt động nếu người dùng chạy tập lệnh không có đặc quyền ghi trên tập lệnh.

Tôi đã trả lời sự phản đối an ninh của mình bằng cách chỉ ra rằng bất kỳ điều khoản đặc biệt được chỉ cần một lần mỗi cài đặt / cập nhật hành động để cài đặt / cập nhật các tự cài đặt kịch bản - mà tôi sẽ đích thân gọi khá an toàn. Tôi cũng chỉ cho anh ta một man shtài liệu tham khảo để đạt được các mục tiêu tương tự bằng các phương tiện tương tự. Vào thời điểm đó, tôi đã không bận tâm chỉ ra rằng bất kỳ lỗi bảo mật nào hoặc nói chung là các thực tiễn không được nêu rõ trong câu trả lời của tôi, chúng có nhiều khả năng bắt nguồn từ chính câu hỏi hơn là trong câu trả lời của tôi:

Làm cách nào tôi có thể thiết lập shebang để chạy tập lệnh dưới dạng /path/to/script.sh luôn sử dụng Zsh có sẵn trong PATH?

Không hài lòng, @ jw013 tiếp tục phản đối bằng cách tiếp tục cuộc tranh luận chưa được hỗ trợ của mình với ít nhất một vài tuyên bố sai lầm:

Bạn sử dụng một tệp duy nhất, không phải hai tệp. Gói [được man shtham chiếu] có một tệp sửa đổi tệp khác. Bạn có một tập tin sửa đổi chính nó. Có một sự khác biệt rõ ràng giữa hai trường hợp này. Một tập tin nhận đầu vào và sản xuất đầu ra là tốt. Một tập tin thực thi tự thay đổi khi nó chạy thường là một ý tưởng tồi. Ví dụ bạn đã chỉ ra không làm điều đó.

Ở nơi đầu tiên:

CÁC CHỈ thực thi MÃ BÊN TRONG BẤT CỨ thực thi SHELL SCRIPT IS #!chính nó

(mặc dù chính thức#!không xác định )

{   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

Một kịch bản shell chỉ là một tập tin văn bản - để cho nó có bất kỳ tác dụng ở tất cả nó phải được đọc bởi một tập tin thực thi, hướng dẫn của nó sau đó giải thích bởi rằng tập tin thực thi khác, trước khi cuối cùng là file thực thi khác sau đó thực hiện việc giải thích của nó trong những kịch bản shell. Không thể thực thi tệp shell shell liên quan đến ít hơn hai tệp. Có một ngoại lệ có thể có trong zshtrình biên dịch riêng của nó, nhưng với điều này tôi có ít kinh nghiệm và nó không có cách nào được trình bày ở đây.

Hashbang của shell script phải trỏ đến trình thông dịch dự định của nó hoặc bị loại bỏ là không liên quan.

CÔNG CỤ TUYỆT VỜI / CÔNG CỤ THỰC HIỆN ĐƯỢC TIÊU CHUẨN

Shell có hai chế độ phân tích cơ bản và diễn giải đầu vào của nó: hoặc đầu vào hiện tại của nó đang xác định một <<here_documenthoặc nó đang xác định một { ( command |&&|| list ) ; } &- nói cách khác, shell sẽ diễn giải mã thông báo như một dấu phân cách cho một lệnh mà nó sẽ thực thi khi nó đã đọc nó trong hoặc như hướng dẫn để tạo một tệp và ánh xạ nó tới một bộ mô tả tệp cho một lệnh khác. Đó là nó.

Khi diễn giải các lệnh để thực thi shell phân định mã thông báo trên một tập hợp các từ dành riêng. Khi vỏ gặp một mở thẻ nó phải tiếp tục đọc trong một danh sách lệnh cho đến khi danh sách được một trong hai giới hạn bởi đường bế thẻ như một dòng mới - khi áp dụng - hoặc đóng cửa mã thông báo như })cho ({trước khi thực hiện.

Shell phân biệt giữa một lệnh đơn giản và một lệnh ghép. Lệnh ghép là tập hợp các lệnh phải được đọc trước khi thực thi, nhưng shell không thực hiện $expansiontrên bất kỳ lệnh đơn giản cấu thành nào của nó cho đến khi nó thực thi từng lệnh một.

Vì vậy, trong ví dụ sau, các ;semicolon từ dành riêng phân định các lệnh đơn giản riêng lẻ trong khi \newlineký tự không thoát được phân định giữa hai lệnh ghép:

{   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

Đó là một sự đơn giản hóa của các hướng dẫn. Nó trở nên phức tạp hơn nhiều khi bạn xem xét các shell-shell, subshells, môi trường hiện tại và vv, nhưng, với mục đích của tôi ở đây, nó là đủ.

Và nói về các danh sách dựng sẵndanh sách lệnh, a function() { declaration ; }chỉ là một phương tiện để gán một lệnh ghép cho một lệnh đơn giản. Shell không được thực hiện bất kỳ $expansionstrên chính câu lệnh khai báo - bao gồm <<redirections>- nhưng thay vào đó phải lưu trữ định nghĩa dưới dạng một chuỗi ký tự đơn và thực hiện nó như một trình bao đặc biệt được tích hợp khi được gọi.

Vì vậy, một hàm shell được khai báo trong tập lệnh shell thực thi được lưu trữ trong bộ nhớ của trình dịch phiên dịch ở dạng chuỗi ký tự của nó - chưa được mở rộng để bao gồm các tài liệu ở đây như là đầu vào - và được thực thi độc lập với tệp nguồn của nó mỗi khi nó được gọi là shell được xây dựng- trong chừng nào môi trường hiện tại của vỏ tồn tại.

Một <<HERE-DOCUMENTLÀ MỘT INLINE FILE

Các toán tử chuyển hướng <<<<-cả hai đều cho phép chuyển hướng các dòng có trong tệp đầu vào shell, được gọi là tài liệu ở đây, đến đầu vào của lệnh.

Các đây-tài liệu sẽ được coi là một từ duy nhất mà bắt đầu sau khi tiếp theo \newlinevà tiếp tục cho đến khi có một dòng chỉ chứa các dấu phân cách và một \newline, không có [:blank:]s ở giữa. Sau đó, tài liệu tiếp theo ở đây bắt đầu, nếu có. Định dạng như sau:

[n]<<word
    here-document 
delimiter

... Trong đó tùy chọn nđại diện cho số mô tả tập tin. Nếu số bị bỏ qua, tài liệu ở đây đề cập đến đầu vào tiêu chuẩn (mô tả tệp 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

Bạn thấy sao? Đối với mỗi shell phía trên shell sẽ tạo một tệp và ánh xạ nó tới một mô tả tệp. Trong zsh, (ba)shshell tạo một tệp thông thường /tmp, kết xuất kết xuất, ánh xạ nó tới một bộ mô tả, sau đó xóa /tmptệp để bản sao của bộ mô tả là tất cả những gì còn lại. dashtránh tất cả những điều vô nghĩa đó và chỉ cần bỏ quá trình xử lý đầu ra của nó vào một |pipetệp ẩn danh nhằm vào <<mục tiêu chuyển hướng .

Điều này làm cho dash:

cmd <<HEREDOC
    $(cmd)
HEREDOC

chức năng tương đương với bash's:

cmd <(cmd)

trong khi dashthực hiện ít nhất là POSIXly xách tay.

MỌI PHIM TUYỆT VỜI

Vì vậy, trong câu trả lời dưới đây khi tôi làm:

{    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

Sau đây xảy ra:

  1. Tôi lần đầu tiên catnội dung của bất cứ tập tin vỏ tạo ra cho FILEvào ./file, làm cho nó thực thi, sau đó thực hiện nó.

  2. Nhân thông dịch #!và các cuộc gọi /usr/bin/shvới một bộ <read mô tả tệp được gán cho ./file.

  3. shánh xạ một chuỗi vào bộ nhớ bao gồm lệnh ghép bắt đầu từ _fn()và kết thúc tại SCRIPT.

  4. Khi _fnđược gọi, shtrước tiên phải giải thích sau đó bản đồ đến một bộ mô tả tập tin quy định tại <<SCRIPT...SCRIPT trước gọi _fnlà đặc biệt tích hợp tiện ích vì SCRIPT_fn's<input.

  5. Các chuỗi đầu ra bởi printfcommandđược viết ra để _fn's chuẩn-out >&1 - được chuyển hướng đến vỏ của hiện tại ARGV0- hoặc $0.

  6. catnối chuỗi mô tả tệp <&0 đầu vào tiêu chuẩn của nó - SCRIPT- qua đối số >của shell hiện tại bị cắt bớt ARGV0, hoặc $0.

  7. Hoàn thành lệnh ghép hiện tại đã đọc của nó , sh execlà đối số thực thi - và mới được viết lại - $0.

Từ lúc ./fileđược gọi cho đến khi các lệnh được chứa của nó xác định rằng nó sẽ được execlặp lại, shđọc nó trong một lệnh ghép duy nhất tại một thời điểm khi nó thực thi chúng, trong khi ./filebản thân nó không làm gì cả ngoại trừ vui vẻ chấp nhận nội dung mới của nó. Các tập tin thực sự đang làm việc là/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.

CẢM ƠN, SAU TẤT CẢ

Vì vậy, khi @ jw013 chỉ định rằng:

Một tập tin nhận đầu vào và tạo đầu ra là tốt ...

... giữa những lời chỉ trích sai lầm của anh ấy về câu trả lời này, anh ấy thực sự đang vô tình bỏ qua phương pháp duy nhất được sử dụng ở đây, về cơ bản chỉ là:

cat <new_file >old_file

CÂU TRẢ LỜI

Tất cả các câu trả lời ở đây là tốt, nhưng không có câu trả lời nào là hoàn toàn chính xác. Mọi người dường như tuyên bố bạn không thể linh hoạt và vĩnh viễn con đường của bạn #!bang. Đây là một minh chứng về việc thiết lập một shebang độc lập đường dẫn:

BẢN GIỚI THIỆU

{   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

ĐẦU RA

        $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\\...

Bạn thấy sao? Chúng tôi chỉ làm cho kịch bản ghi đè lên chính nó. Và nó chỉ xảy ra một lần sau khi gitđồng bộ hóa. Từ thời điểm đó, nó đã đi đúng đường trong dòng #! Bang.

Bây giờ hầu như tất cả những thứ đó chỉ là lông tơ. Để làm điều này một cách an toàn, bạn cần:

  1. Một chức năng được xác định ở trên cùng và được gọi ở dưới cùng mà viết. Bằng cách này, chúng tôi lưu trữ mọi thứ chúng tôi cần trong bộ nhớ và đảm bảo toàn bộ tệp được đọc trước khi chúng tôi bắt đầu viết lên nó.

  2. Một số cách xác định những gì các con đường nên được. command -vlà khá tốt cho điều đó.

  3. Heredocs thực sự hữu ích vì chúng là các tệp thực tế. Họ sẽ lưu trữ tập lệnh của bạn trong lúc này. Bạn cũng có thể sử dụng chuỗi nhưng ...

  4. Bạn phải chắc chắn rằng trình bao đọc trong lệnh ghi đè lên tập lệnh của bạn trong cùng danh sách lệnh với lệnh thực thi nó.

Nhìn:

{   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

Lưu ý rằng tôi chỉ di chuyển execlệnh xuống một dòng. Hiện nay:

#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

Tôi không nhận được nửa sau của đầu ra vì tập lệnh không thể đọc được trong lệnh tiếp theo. Tuy nhiên, vì lệnh duy nhất bị thiếu là lệnh cuối cùng:

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\\...

Kịch bản xuất hiện đúng như mong muốn - chủ yếu là vì tất cả đều nằm trong di sản - nhưng nếu bạn không lên kế hoạch đúng, bạn có thể cắt đoạn phim của mình, đó là những gì đã xảy ra với tôi ở trên.


Downvote là bởi vì mã tự sửa đổi thường được coi là thực hành xấu. Quay lại thời kỳ cũ của các chương trình lắp ráp nhỏ, đó là một cách thông minh để giảm các nhánh có điều kiện và cải thiện hiệu suất, nhưng ngày nay các rủi ro bảo mật vượt xa các lợi thế. Cách tiếp cận của bạn sẽ không hoạt động nếu người dùng chạy tập lệnh không có đặc quyền ghi trên tập lệnh.
jw013

@ jw013 Rõ ràng cách tiếp cận của tôi để cài đặt hoặc cập nhật tập lệnh thực thi sẽ không hoạt động nếu người cố cài đặt hoặc cập nhật tập lệnh không có quyền cài đặt hoặc cập nhật tập lệnh. thực tế, đó là điều đặc biệt làm cho câu trả lời này tốt hơn mọi câu trả lời khác ở đây - nó có thể cung cấp một dòng #! bang chính xác khi cần thiết và chỉ cần bất kỳ quyền đặc biệt nào để thực hiện trong lần gọi đầu tiên - trong khi cài đặt. Và, một lần nữa, tôi sẽ không đơn giản nhận lời của bạn rằng mã tự sửa đổi là thông lệ xấu - vui lòng xem man commandđể biết ý kiến ​​trái ngược.
mikeerv

xin vui lòng xem man commandcho một ý kiến ​​trái ngược - không tìm thấy một. Bạn có thể hướng tôi đến phần / đoạn cụ thể mà bạn đang nói không?
jw013

@ jw013 - lỗi của tôi, đó là man sh- tìm kiếm 'lệnh -v'. Tôi biết nó ở một trong những mantrang tôi đang xem vào một ngày khác.
mikeerv

Tôi cho rằng đâycommand -vví dụ bạn đã nói về man sh. Đó là một tập lệnh cài đặt trông bình thường, và không phải là một tập lệnh tự sửa đổi . Ngay cả các trình cài đặt độc lập chỉ chứa đầu vào sửa đổi trước và xuất các sửa đổi của chúng ở một nơi khác. Họ không viết lại theo cách bạn đang đề xuất.
jw013

1

Đây là một cách để có một kịch bản tự sửa đổi sửa lỗi shebang của nó. Mã này nên được thêm vào kịch bản thực tế của bạn.

#!/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

Một vài bình luận:

  • Nó nên được chạy một lần bởi một người có quyền tạo và xóa các tệp trong thư mục chứa tập lệnh.

  • Nó chỉ sử dụng cú pháp shell bourne kế thừa, mặc dù niềm tin phổ biến, /bin/shkhông được đảm bảo là shell POSIX, thậm chí cả các hệ điều hành tuân thủ POSIX.

  • Nó đặt PATH thành một vị trí tuân thủ POSIX theo sau là danh sách các vị trí zsh có thể để tránh chọn zsh "giả mạo".

  • Nếu vì một lý do nào đó, một kịch bản tự sửa đổi là không phù hợp, thì việc phân phối hai tập lệnh thay vì một tập lệnh thay vì tập lệnh đầu tiên là tập lệnh bạn muốn vá và tập lệnh thứ hai, tập lệnh tôi đề nghị sửa đổi một chút để xử lý tập lệnh trước.


Vấn đề /bin/shlà một điều tốt - nhưng trong trường hợp đó bạn có cần một tiền tố #!nào không? Và không phải awklà có khả năng là giả mạo như zshlà?
mikeerv

@mikeerv Trả lời được cập nhật để gọi cho POSIX awk. Shebang được chuẩn bị sẵn có để ngăn đoạn script được giải thích bởi một shell tương thích không phải bourne nếu nó là shell đăng nhập của bạn.
jlliagre

Có ý nghĩa. Tôi đã nâng cấp nó bởi vì nó hoạt động, nó bám sát vào cuốn sách và nó thể hiện sự hiểu biết âm thanh về khả năng xử lý tệp / môi trường shell có thể - đặc biệt là các tệp sao lưu mà bạn sử dụng, đó là tất cả những gì GNU sed -ithực hiện. Cá nhân tôi nghĩ rằng $PATHvấn đề được ghi nhận trong các nhận xét về một câu trả lời khác và mà bạn giải quyết một cách an toàn như tôi có thể tìm thấy ở đây trong một vài dòng được xử lý tốt hơn bằng cách xác định đơn giản và rõ ràng các phụ thuộc và / hoặc kiểm tra nghiêm ngặt và rõ ràng - ví dụ, bây giờ getconfcó thể là giả mạo, nhưng cơ hội gần như không, giống như họ đã từng zshawk.
mikeerv

@mikeerv, tập lệnh được sửa đổi để giảm rủi ro để gọi một getconf không có thật.
jlliagre

$(getconf PATH)không phải là Bourne. cp $0 $0.oldlà cú pháp zsh. Tương đương Bourne sẽ là cp "$0" "$0.old"mặc dù bạn muốncp -- "$0" "$0.old"
Stéphane Chazelas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.