Bash: meneruskan fungsi sebagai parameter

91

Saya harus meneruskan fungsi sebagai parameter di Bash. Misalnya kode berikut ini:

function x() {
  echo "Hello world"
}

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

around x

Haruskah mengeluarkan:

before
Hello world
after

Saya tahu evaltidak benar dalam konteks itu tapi itu hanya sebuah contoh :)

Ada ide?

cd1
sumber

Jawaban:

126

Jika Anda tidak memerlukan sesuatu yang mewah seperti menunda evaluasi nama fungsi atau argumennya, Anda tidak perlu eval:

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

around x

melakukan apa yang kamu inginkan. Anda bahkan dapat meneruskan fungsi dan argumennya dengan cara ini:

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

around x 1st 2nd

cetakan

before
x(): Passed 1st and 2nd
after
Idelic
sumber
2
Jika saya memiliki fungsi y () lain, dapatkah saya melakukan sekitar x 1st 2nd y 1st 2nd? Bagaimana ia mengetahui bahwa x dan y adalah argumen untuk sekitar sedangkan 1 dan 2 adalah argumen untuk x dan y?
techguy2000
Dan Anda bisa menghilangkan kata fungsi.
jasonleonhard
Namun, mereka tidak akan diberi spasi nama. yaitu Jika Anda memiliki metode di dalam metode dan Anda menyimpan functionkata tersebut, Anda tidak dapat mengakses metode internal tersebut sampai Anda menjalankan metode tingkat atas.
jasonleonhard
29

Saya tidak berpikir ada orang yang menjawab pertanyaan itu. Dia tidak bertanya apakah dia bisa menggemakan string secara berurutan. Sebaliknya, penulis pertanyaan ingin tahu apakah dia dapat mensimulasikan perilaku penunjuk fungsi.

Ada beberapa jawaban yang mirip dengan yang saya lakukan, dan saya ingin mengembangkannya dengan contoh lain.

Dari penulis:

function x() {
  echo "Hello world"
}

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

around x

Untuk memperluas ini, kita akan memiliki fungsi x echo "Hello world: $ 1" untuk menunjukkan kapan eksekusi fungsi benar-benar terjadi. Kami akan mengirimkan string yang merupakan nama fungsi "x":

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

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

around x

Untuk menggambarkan ini, string "x" dilewatkan ke fungsi around () yang menggemakan "sebelum", memanggil fungsi x (melalui variabel $ 1, parameter pertama diteruskan ke sekitar) meneruskan argumen "DI SINI", akhirnya bergema setelah .

Selain itu, ini adalah metodologi untuk menggunakan variabel sebagai nama fungsi. Variabel sebenarnya menahan string yang merupakan nama fungsi dan ($ variabel arg1 arg2 ...) memanggil fungsi yang meneruskan argumen. Lihat di bawah:

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

Z="x"        # or just Z=x

($Z  10 20 30)

memberikan: 30 10 20, di mana kami menjalankan fungsi bernama "x" yang disimpan dalam variabel Z dan melewati parameter 10 20 dan 30.

Di atas di mana kita mereferensikan fungsi dengan menetapkan nama variabel ke fungsi sehingga kita dapat menggunakan variabel sebagai pengganti benar-benar mengetahui nama fungsi (yang mirip dengan apa yang mungkin Anda lakukan dalam situasi penunjuk fungsi yang sangat klasik di c untuk menggeneralisasi aliran program tetapi pra -memilih panggilan fungsi yang akan Anda buat berdasarkan argumen baris perintah).

Di bash ini bukan penunjuk fungsi, tetapi variabel yang merujuk ke nama fungsi yang nanti Anda gunakan.

uDude
sumber
Jawaban ini luar biasa. Saya membuat skrip bash dari semua contoh dan menjalankannya. Saya juga sangat suka bagaimana Anda membuat pembuat "hanya perubahan" yang sangat membantu. Paragraf kedua hingga terakhir memiliki kesalahan ejaan: "Di atas"
JMI MADISON
Salah ketik diperbaiki. Terima kasih @J MADISON
uDude
7
mengapa Anda membungkusnya ()bukankah itu memulai sub-shell?
horseyguy
17

tidak perlu digunakan eval

function x() {
  echo "Hello world"
}

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

around x
kurumi
sumber
5

Anda tidak dapat meneruskan apa pun ke fungsi selain string. Proses substitusi dapat memalsukannya. Bash cenderung menahan FIFO sampai perintahnya diperluas hingga selesai.

Ini yang konyol

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)

Fungsi dapat diekspor, tetapi ini tidak semenarik yang pertama kali muncul. Saya merasa ini terutama berguna untuk membuat fungsi debugging dapat diakses oleh skrip atau program lain yang menjalankan skrip.

(
    id() {
        "$@"
    }

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

id masih hanya mendapatkan string yang kebetulan menjadi nama fungsi (diimpor secara otomatis dari serialisasi di lingkungan) dan argumennya.

Komentar Pumbaa80 untuk jawaban lain juga bagus ( eval $(declare -F "$1")), tetapi berguna terutama untuk array, bukan fungsi, karena selalu global. Jika Anda menjalankan ini dalam suatu fungsi, yang akan dilakukannya hanyalah mendefinisikan ulang, jadi tidak ada efeknya. Ini tidak dapat digunakan untuk membuat penutupan atau fungsi parsial atau "contoh fungsi" bergantung pada apa pun yang terjadi untuk terikat dalam cakupan saat ini. Paling banter ini dapat digunakan untuk menyimpan definisi fungsi dalam string yang akan didefinisikan ulang di tempat lain - tetapi fungsi tersebut juga hanya dapat di-hardcode kecuali tentu saja evaldigunakan

Pada dasarnya Bash tidak bisa digunakan seperti ini.

ormaaj
sumber
2

Pendekatan yang lebih baik adalah menggunakan variabel lokal dalam fungsi Anda. Masalahnya kemudian menjadi bagaimana Anda menyampaikan hasilnya kepada penelepon. Salah satu mekanismenya adalah dengan menggunakan substitusi perintah:

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

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

Di sini hasilnya adalah output ke stdout dan pemanggil menggunakan substitusi perintah untuk menangkap nilai dalam variabel. Variabel tersebut kemudian dapat digunakan sesuai kebutuhan.

Anand Thangappan
sumber
1

Anda harus memiliki sesuatu di sepanjang baris:

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

Anda kemudian dapat menelepon around x

Tim O
sumber
-1

eval mungkin satu-satunya cara untuk mencapainya. Satu-satunya kelemahan nyata adalah aspek keamanannya, karena Anda perlu memastikan bahwa tidak ada yang berbahaya yang masuk dan hanya fungsi yang ingin Anda panggil yang akan dipanggil (bersama dengan memeriksa bahwa tidak ada karakter jahat seperti ';' di dalamnya juga).

Jadi jika Anda yang memanggil kode tersebut, maka kemungkinan besar eval adalah satu-satunya cara untuk melakukannya. Perhatikan bahwa ada bentuk eval lain yang kemungkinan akan berfungsi terlalu melibatkan subperintah ($ () dan ``), tetapi tidak lebih aman dan lebih mahal.

Wes Hardaker
sumber
eval adalah satu-satunya cara untuk melakukannya.
Wes
1
Anda dapat dengan mudah memeriksa apakah eval $1akan memanggil fungsi menggunakanif declare -F "$1" >/dev/null; then eval $1; fi
user123444555621
2
... atau bahkan lebih baik:eval $(declare -F "$1")
user123444555621