Bash: truyền một hàm làm tham số


91

Tôi cần truyền một hàm dưới dạng tham số trong Bash. Ví dụ, đoạn mã sau:

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  eval $1
  echo "after"
}

around x

Nên xuất:

before
Hello world
after

Tôi biết evallà không đúng trong bối cảnh đó nhưng đó chỉ là một ví dụ :)

Bất kỳ ý tưởng?

Câu trả lời:


126

Nếu bạn không cần bất cứ điều gì ưa thích như trì hoãn việc đánh giá tên hàm hoặc các đối số của nó, bạn không cần eval:

function x()      { echo "Hello world";          }
function around() { echo before; $1; echo after; }

around x

làm những gì bạn muốn. Bạn thậm chí có thể chuyển hàm và các đối số của nó theo cách này:

function x()      { echo "x(): Passed $1 and $2";  }
function around() { echo before; "$@"; echo after; }

around x 1st 2nd

bản in

before
x(): Passed 1st and 2nd
after

2
Nếu tôi có một hàm y () khác, tôi có thể làm xung quanh x thứ nhất thứ 2 y thứ nhất thứ 2 không? Làm thế nào nó biết x và y là các đối số xung quanh trong khi 1 và 2 là các đối số cho x và y?
techguy2000

Và bạn có thể bỏ từ chức năng.
jasonleonhard 19/09/19

Tuy nhiên, sau đó chúng sẽ không có khoảng cách giữa tên. tức là Nếu bạn có các phương thức bên trong các phương thức và bạn giữ nguyên thông functionbáo rằng bạn không thể truy cập các phương thức bên trong đó cho đến khi bạn chạy phương thức cấp cao nhất.
jasonleonhard 19/09/19

29

Tôi không nghĩ có ai trả lời được câu hỏi. Anh ta không hỏi liệu anh ta có thể lặp lại các chuỗi theo thứ tự hay không. Thay vào đó, tác giả của câu hỏi muốn biết liệu anh ta có thể mô phỏng hành vi của con trỏ hàm hay không.

Có một số câu trả lời giống như những gì tôi sẽ làm và tôi muốn mở rộng nó bằng một ví dụ khác.

Từ tác giả:

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  ($1)                   <------ Only change
  echo "after"
}

around x

Để mở rộng điều này, chúng ta sẽ có hàm x echo "Hello world: $ 1" để hiển thị thời điểm thực thi hàm thực sự xảy ra. Chúng ta sẽ chuyển một chuỗi là tên của hàm "x":

function x() {
  echo "Hello world:$1"
}

function around() {
  echo "before"
  ($1 HERE)                   <------ Only change
  echo "after"
}

around x

Để mô tả điều này, chuỗi "x" được truyền cho hàm xung quanh () mà nó lặp lại là "trước", gọi hàm x (thông qua biến $ 1, tham số đầu tiên được truyền vào xung quanh) truyền đối số "HERE", cuối cùng vọng lại sau .

Ngoài ra, đây là phương pháp sử dụng các biến làm tên hàm. Các biến thực sự giữ chuỗi là tên của hàm và ($ biến arg1 arg2 ...) gọi hàm truyền các đối số. Xem bên dưới:

function x(){
    echo $3 $1 $2      <== just rearrange the order of passed params
}

Z="x"        # or just Z=x

($Z  10 20 30)

cho: 30 10 20, trong đó chúng tôi thực thi hàm có tên "x" được lưu trữ trong biến Z và truyền các tham số 10 20 và 30.

Ở trên, nơi chúng tôi tham chiếu các hàm bằng cách gán tên biến cho các hàm để chúng tôi có thể sử dụng biến thay vì thực sự biết tên hàm (tương tự như những gì bạn có thể làm trong tình huống con trỏ hàm rất cổ điển trong c để tổng quát hóa luồng chương trình nhưng trước -chọn các lệnh gọi hàm mà bạn sẽ thực hiện dựa trên các đối số dòng lệnh).

Trong bash, đây không phải là các con trỏ hàm, mà là các biến tham chiếu đến tên của các hàm mà bạn sử dụng sau này.


Câu trả lời này là tuyệt vời. Tôi đã tạo tập lệnh bash của tất cả các ví dụ và chạy chúng. Tôi cũng thực sự thích cách bạn làm cho những người tạo ra "chỉ thay đổi", điều này đã giúp rất nhiều. Thứ hai của bạn để đoạn cuối cùng có một mis-chính tả: "Aabove"
JMI MADISON

Đã sửa lỗi đánh máy. Cảm ơn @J MADISON
uDude 14/09/17

7
tại sao bạn quấn nó vào ()sẽ không bắt đầu một trình bao phụ?
Horseyguy

17

không cần sử dụng eval

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  var=$($1)
  echo "after $var"
}

around x

5

Bạn không thể chuyển bất cứ thứ gì cho một hàm khác ngoài chuỗi. Thay thế quy trình có thể làm giả nó. Bash có xu hướng giữ mở FIFO cho đến khi một lệnh được mở rộng của nó hoàn thành.

Đây là một câu chuyện ngớ ngẩn nhanh chóng

foldl() {
    echo $(($(</dev/stdin)$2))
} < <(tr '\n' "$1" <$3)

# Sum 20 random ints from 0-999
foldl + 0 <(while ((n=RANDOM%999,x++<20)); do echo $n; done)

Các hàm có thể được xuất, nhưng điều này không thú vị như lần đầu xuất hiện. Tôi thấy nó chủ yếu hữu ích để làm cho các chức năng gỡ lỗi có thể truy cập vào các tập lệnh hoặc các chương trình khác chạy tập lệnh.

(
    id() {
        "$@"
    }

    export -f id
    exec bash -c 'echowrap() { echo "$1"; }; id echowrap hi'
)

id vẫn chỉ nhận được một chuỗi là tên của một hàm (được nhập tự động từ tuần tự hóa trong môi trường) và các chuỗi ký tự của nó.

Nhận xét của Pumbaa80 cho một câu trả lời khác cũng tốt ( eval $(declare -F "$1")), nhưng nó chủ yếu hữu ích cho các mảng chứ không phải các hàm, vì chúng luôn toàn cục. Nếu bạn chạy điều này trong một chức năng, tất cả những gì nó sẽ làm là xác định lại nó, vì vậy không có tác dụng gì. Nó không thể được sử dụng để tạo bao đóng hoặc một phần chức năng hoặc "phiên bản chức năng" phụ thuộc vào bất kỳ điều gì xảy ra bị ràng buộc trong phạm vi hiện tại. Điều này tốt nhất có thể được sử dụng để lưu trữ một định nghĩa hàm trong một chuỗi được định nghĩa lại ở nơi khác - nhưng những hàm đó cũng chỉ có thể được mã hóa cứng trừ khi tất nhiên evalđược sử dụng

Về cơ bản không thể sử dụng Bash như thế này.


2

Một cách tiếp cận tốt hơn là sử dụng các biến cục bộ trong các hàm của bạn. Vấn đề sau đó trở thành làm thế nào để bạn nhận được kết quả cho người gọi. Một cơ chế là sử dụng thay thế lệnh:

function myfunc()
{
    local  myresult='some value'
    echo "$myresult"
}

result=$(myfunc)   # or result=`myfunc`
echo $result

Ở đây kết quả được xuất ra stdout và người gọi sử dụng thay thế lệnh để nắm bắt giá trị trong một biến. Biến sau đó có thể được sử dụng khi cần thiết.


1

Bạn nên có một cái gì đó dọc theo dòng:

function around()
{
  echo 'before';
  echo `$1`;
  echo 'after';
}

Sau đó bạn có thể gọi around x


-1

eval có thể là cách duy nhất để hoàn thành nó. Nhược điểm thực sự duy nhất là khía cạnh bảo mật của nó, vì bạn cần đảm bảo rằng không có gì độc hại được truyền vào và chỉ các hàm bạn muốn gọi mới được gọi (cùng với việc kiểm tra xem nó không có các ký tự khó chịu như ';' trong đó cũng vậy).

Vì vậy, nếu bạn là người gọi mã, thì eval có thể là cách duy nhất để làm điều đó. Lưu ý rằng có những hình thức đánh giá khác có thể sẽ hoạt động liên quan đến các lệnh con ($ () và ``), nhưng chúng không an toàn hơn và đắt hơn.


eval là cách duy nhất để làm điều đó.
Wes

1
Bạn có thể dễ dàng kiểm tra xem eval $1có gọi một hàm bằng cách sử dụngif declare -F "$1" >/dev/null; then eval $1; fi
user123444555621

2
... hoặc thậm chí tốt hơn:eval $(declare -F "$1")
user123444555621
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.