Bash - Hàm trong Biến Shell


7

Tôi đang đọc bài viết này về việc sử dụng các hàm trong các biến bash shell. Tôi quan sát rằng để sử dụng các hàm shell, người ta phải xuất chúng và thực thi chúng trong một shell con như sau:

$ export foo='() { echo "Inside function"; }'
$ bash -c 'foo'
Inside function

Tôi muốn hỏi liệu các hàm có thể được xác định trong các biến bash shell sao cho chúng có thể được thực thi trong shell hiện tại mà không cần sử dụng export & chạy shell mới không?


3
Có chuyện gì với chỉ foo() { echo "Inside function"; }; foovậy?
cuonglm

Không, ý tôi là nếu có thể định nghĩa nó và chạy nó trong cùng một vỏ nhưng sau đó (nói sau khi chạy một vài lệnh khác)
Jake

1
Cú pháp trên là chính xác xác định nó và chạy nó sau . Chỉ cần xác định hàm nguyên trạng, bạn chỉ cần xuất nếu bạn muốn nó có sẵn trong vỏ phụ. Và lưu ý rằng ví dụ của bạn sẽ không hoạt động nữa sau khi shellshock được sửa. Bạn phải xác định chức năng sau đó xuất nó một cách rõ ràng.
cuonglm

Nếu bạn có được chức năng một tên. $ function bar() { echo "Inside function"; } $ foo="bar" $ ${foo} Inside function $
Andy Dalton

Câu trả lời:


8

Không, bạn không thể.

Nếu bạn muốn sử dụng một hàm trong shell hiện tại, chỉ cần xác định nó sau đó sử dụng nó sau:

$ foo() { echo "Inside function"; }
$ foo
Inside function

Trước ngày Shellshock , bạn có thể lưu trữ hàm bên trong một biến, xuất biến và sử dụng hàm trong lớp vỏ phụ, bởi vì bashchức năng xuất hỗ trợ, bashsẽ đặt định nghĩa hàm trong biến môi trường như:

foo=() {
  echo "Inside function"
}

sau đó giải thích hàm bằng cách thay thế =bằng khoảng trắng. Không có ý định đưa hàm vào biến và tham chiếu đến biến sẽ thực thi hàm.

Bây giờ, sau khi Stéphane Chazelas tìm thấy lỗi, tính năng đó đã bị xóa , không còn định nghĩa hàm được lưu trữ trong biến môi trường đơn giản nữa. Bây giờ các hàm được xuất sẽ được mã hóa bằng cách nối thêm tiền tố BASH_FUNC_và hậu tố %%để tránh xung đột với các biến môi trường. bashtrình thông dịch bây giờ có thể xác định xem chúng có phải là hàm shell hay không bất kể nội dung của biến. Bạn cũng cần xác định hàm và xuất nó một cách rõ ràng để sử dụng nó trong lớp vỏ phụ:

$ foo() { echo "Inside function"; }
$ export -f foo
$ bash -c foo
Inside function

Dù sao, nếu ví dụ của bạn làm việc với hiện tại của bạn bash, thì bạn đang sử dụng một phiên bản dễ bị tấn công.


2
BTW: Tính năng này luôn mâu thuẫn với tiêu chuẩn POSIX.
schily

1
Gần đây tôi đã thực hiện export, unexportvà các unsetnội trang từ Bourne Shell tuân thủ POSIX và một nơi nào đó trong các nội dung này là lời giải thích, tôi tin vào export.
schily

1
@cuonglm Có trang man nào mô tả cách sử dụng các hàm trong bash không?
Jake

1
@Jake - man bash. Khi bạn mở nó, bấm /phím và sử dụng biểu thức chính quy ^[[:space:]]*Funcđể tìm dòng bắt đầu bằng cụm từ đó. Nhấn nđể bỏ qua các trận đấu để tìm kiếm của bạn. và đánh giá bằng câu hỏi của bạn, bạn cũng có thể muốn xem xét alias.
mikeerv

1
@Jake: Dấu chấm phẩy chỉ là dấu kết thúc, được sử dụng để phân tách mã thông báo shell, đây là lệnh và lệnh }. bashtài liệu có một phần chi tiết về chức năng .
cuonglm

5
foo='foo(){ echo "Inside Function"; }'
bash -c "$foo; foo"

Inside Function

Cuối cùng, một hàm chỉ đơn giản là một chuỗi lệnh tĩnh, được phân tích trước, được lưu trữ trong bộ nhớ của shell. Và do đó, cách thực sự duy nhất để làm điều đó là bằng cách đánh giá các chuỗi dưới dạng mã, đó chính xác là những gì shell thực hiện mỗi khi bạn gọi hàm của mình.

Nó chưa bao giờ rất khác trước đây, và nó vẫn không. Các nhà phát triển thiết lập các phương thức thuận tiện để làm như vậy và họ cố gắng bảo vệ các lỗ hổng bảo mật có thể có, nhưng thực tế của vấn đề là một thứ càng trở nên thuận tiện, bạn càng biết ít về nó hơn bạn nên làm.

Có rất nhiều tùy chọn có sẵn cho bạn khi nhập dữ liệu từ shell cha vào subshells. Làm quen với chúng, trở nên đủ tự tin để bạn có thể xác định những công dụng tiềm năng và những điểm yếu tiềm ẩn của chúng, và sử dụng chúng một cách tiết kiệm nhưng hiệu quả.


Tôi cho rằng tôi có thể giải thích một hoặc hai trong số này trong khi tôi đang ở đó. Có lẽ cách tốt nhất để truyền các lệnh tĩnh từ shell này sang shell khác (cho dù đó là lên hay xuống chuỗi lệnh subshelling)alias. aliaslà hữu ích trong khả năng này vì nó được chỉ định POSIX để báo cáo các lệnh được xác định của nó một cách an toàn :

Định dạng để hiển thị bí danh (khi không có toán hạng hoặc chỉ các toán hạng tên được chỉ định) sẽ là:

  • "%s=%s\n", name, value

Chuỗi giá trị phải được viết với trích dẫn thích hợp để nó phù hợp cho việc cung cấp lại cho trình bao. Xem mô tả trích dẫn shell trong Trích dẫn .

Ví dụ:

alias foo="foo(){ echo 'Inside Function'; }"
alias foo

foo='foo(){ echo '\''Inside Function'\''; }'

Xem những gì nó làm với dấu ngoặc kép, có? Đầu ra chính xác phụ thuộc vào shell, nhưng, nói chung, bạn có thể dựa vào shell báo cáo các aliasđịnh nghĩa của nó theo cách evalan toàn để cung cấp lại cho cùng một shell. Tuy nhiên, việc trộn và kết hợp các vỏ nguồn / đích có thể là iffy trong một số trường hợp.

Để chứng minh một nguyên nhân để thực hiện thận trọng như vậy:

ksh -c 'alias foo="
    foo(){
        echo \"Inside Function\"
    }"
    alias foo'

foo=$'\nfoo(){\n\techo "Inside Function"\n}'

Cũng lưu ý rằng aliaskhông gian tên đại diện cho một trong ba không gian tên độc lập mà bạn thường có thể mong đợi để tìm thấy hoàn toàn xác thực trong bất kỳ lớp vỏ hiện đại nào. Các shell thường cung cấp cho bạn ba không gian tên này:

  • Các biến: name=valuengoài danh sách

    • Đây cũng có thể được xác định với một số lệnh nội trú như export, set, readonlytrong một số trường hợp.
  • Chức năng: name() compound_command <>any_redirectsvị trí lệnh

    • Shell được chỉ định không mở rộng hoặc giải thích bất kỳ phần nào của định nghĩa hàm mà nó có thể tìm thấy trong lệnh tại thời điểm định nghĩa.
      • Lệnh cấm này không bao gồm các bí danh, tuy nhiên, vì chúng được mở rộng trong quá trình phân tích cú pháp đọc lệnh của shell, và do đó alias, các giá trị, nếu aliasđược tìm thấy trong ngữ cảnh chính xác, sẽ được mở rộng thành lệnh định nghĩa hàm khi tìm thấy.
    • Như đã đề cập, shell lưu giá trị được phân tích cú pháp của lệnh định nghĩa dưới dạng một chuỗi tĩnh trong bộ nhớ của nó và nó thực hiện tất cả các mở rộng và chuyển hướng được tìm thấy trong chuỗi chỉ khi tên hàm sau đó được tìm thấy sau khi phân tích cú pháp.
  • Bí danh: alias name=valuevị trí chỉ huy

    • Giống như các hàm, các aliasđịnh nghĩa được diễn giải khi các tên định nghĩa được tìm thấy sau đó ở vị trí lệnh.
    • Tuy nhiên, không giống như các hàm, vì định nghĩa của chúng là đối số của aliaslệnh, các mở rộng shell hợp lệ được tìm thấy bên trong cũng được diễn giải theo thời gian xác định. Và do đó, aliasđịnh nghĩa luôn luôn được giải thích hai lần.
    • Cũng không giống như các hàm, các aliasđịnh nghĩa hoàn toàn không được phân tích cú pháp, nhưng, đúng hơn, đó là trình phân tích cú pháp của shell mở rộng các giá trị của chúng trước khi phân tích thành một chuỗi lệnh khi chúng được tìm thấy.

Bạn có thể nhận thấy những gì tôi coi là sự khác biệt chính giữa một aliashoặc một chức năng ở trên, đó là điểm mở rộng của trình bao cho mỗi. Sự khác biệt, thực sự, có thể làm cho sự kết hợp của họ khá hữu ích.

alias foo='
    foo(){
        printf "Inside Function: %s\n" "$@"
        unset -f foo
    };  foo "$@" '

Đó là một bí danh sẽ xác định một chức năng tự hủy cài đặt khi được gọi và do đó, nó ảnh hưởng đến một không gian tên bằng ứng dụng của một không gian tên khác. Sau đó, nó gọi hàm. Ví dụ là ngây thơ; nó không hữu ích lắm trong ngữ cảnh thông thường, nhưng ví dụ sau có thể chứng minh một số tính hữu dụng hạn chế khác mà nó có thể cung cấp trong các bối cảnh khác:

sh -c "
    alias $(alias foo)
    foo \'
" -- \; \' \" \\

Inside Function: ;
Inside Function: '
Inside Function: "
Inside Function: \
Inside Function: '

Bạn phải luôn luôn gọi một aliastên trên một dòng đầu vào khác với tên mà định nghĩa được tìm thấy - một lần nữa, điều này là để thực hiện theo thứ tự phân tích cú pháp của lệnh. Khi được kết hợp, các bí danh và chức năng có thể được sử dụng cùng nhau để di chuyển một cách hiệu quả, an toàn và hiệu quả các mảng thông tin và tham số trong và ngoài các bối cảnh được chia nhỏ.


Hầu như không liên quan, nhưng đây là một niềm vui khác:

alias mlinesh='"${SHELL:-sh}" <<"" '

Chạy nó tại một dấu nhắc tương tác và bất kỳ đối số nào bạn chuyển đến nó trên cùng một dòng thực thi như dòng mà bạn gọi nó sẽ được hiểu là đối số cho một lớp con. Tuy nhiên, tất cả các dòng sau đó cho đến khi dòng trống xuất hiện đầu tiên, được đọc bởi lớp vỏ hiện tại để chuyển dưới dạng stdin sang subshell mà nó chuẩn bị gọi, và không ai trong số chúng được hiểu theo bất kỳ cách nào cả.

$ mlinesh -c '. /dev/fd/0; foo'
> foo(){ echo 'Inside Function'; } 
> 

Inside Function

...nhưng chỉ...

$ mlinesh
> foo(){ echo 'Inside Function'; }
> foo
> 

... dễ dàng hơn ...

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.