Bash: ส่งผ่านฟังก์ชันเป็นพารามิเตอร์


92

ฉันต้องส่งฟังก์ชันเป็นพารามิเตอร์ใน Bash ตัวอย่างเช่นรหัสต่อไปนี้:

function x() {
  echo "Hello world"
}

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

around x

ควรส่งออก:

before
Hello world
after

ฉันรู้ว่าevalไม่ถูกต้องในบริบทนั้น แต่นั่นเป็นเพียงตัวอย่าง :)

ความคิดใด ๆ ?

คำตอบ:


128

หากคุณไม่ต้องการอะไรที่แปลกใหม่เช่นการชะลอการประเมินชื่อฟังก์ชันหรืออาร์กิวเมนต์คุณไม่จำเป็นต้องeval:

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

around x

ทำในสิ่งที่คุณต้องการ คุณสามารถส่งผ่านฟังก์ชันและอาร์กิวเมนต์ได้ด้วยวิธีนี้:

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

around x 1st 2nd

พิมพ์

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

2
ถ้าฉันมีฟังก์ชัน y () อื่นฉันจะทำรอบ x 1st 2nd y 1st 2nd ได้หรือไม่ จะรู้ได้อย่างไรว่า x และ y เป็นอาร์กิวเมนต์ในขณะที่ 1 และ 2 เป็นอาร์กิวเมนต์สำหรับ x และ y
techguy2000

และคุณสามารถละเว้นคำฟังก์ชันได้
jasonleonhard

อย่างไรก็ตามจะไม่มีการเว้นระยะชื่อ กล่าวคือหากคุณมีวิธีการภายในของวิธีการและคุณเก็บfunctionคำไว้คุณจะไม่สามารถเข้าถึงวิธีการภายในเหล่านั้นได้จนกว่าคุณจะเรียกใช้วิธีการระดับบนสุด
jasonleonhard

29

ฉันไม่คิดว่าจะมีใครตอบคำถามได้ เขาไม่ได้ถามว่าเขาสามารถสะท้อนสตริงตามลำดับได้หรือไม่ ผู้เขียนคำถามต้องการทราบว่าเขาสามารถจำลองพฤติกรรมของตัวชี้ฟังก์ชันได้หรือไม่

มีคำตอบสองสามคำที่เหมือนกับสิ่งที่ฉันทำและฉันต้องการขยายความด้วยอีกตัวอย่างหนึ่ง

จากผู้เขียน:

function x() {
  echo "Hello world"
}

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

around x

เพื่อขยายสิ่งนี้เราจะมีฟังก์ชัน x echo "Hello world: $ 1" เพื่อแสดงเมื่อการเรียกใช้ฟังก์ชันเกิดขึ้นจริงๆ เราจะส่งสตริงที่เป็นชื่อของฟังก์ชัน "x":

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

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

around x

เพื่ออธิบายสิ่งนี้สตริง "x" จะถูกส่งไปยังฟังก์ชันรอบ ๆ () ซึ่ง echos "before" เรียกฟังก์ชัน x (ผ่านตัวแปร $ 1 พารามิเตอร์แรกที่ส่งผ่านไปยังรอบ ๆ ) ผ่านอาร์กิวเมนต์ "HERE" ในที่สุด echos หลังจาก .

นอกเหนือจากนี้เป็นวิธีการใช้ตัวแปรเป็นชื่อฟังก์ชัน ตัวแปรถือสตริงที่เป็นชื่อของฟังก์ชันและ (ตัวแปร $ arg1 arg2 ... ) เรียกใช้ฟังก์ชันที่ส่งผ่านอาร์กิวเมนต์ ดูด้านล่าง:

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

Z="x"        # or just Z=x

($Z  10 20 30)

ให้: 30 10 20 โดยที่เราเรียกใช้ฟังก์ชันชื่อ "x" ที่เก็บไว้ในตัวแปร Z และส่งผ่านพารามิเตอร์ 10 20 และ 30

ด้านบนที่เราอ้างอิงฟังก์ชันโดยการกำหนดชื่อตัวแปรให้กับฟังก์ชันเพื่อให้เราสามารถใช้ตัวแปรแทนการรู้ชื่อฟังก์ชันได้จริง (ซึ่งคล้ายกับสิ่งที่คุณอาจทำในสถานการณ์ตัวชี้ฟังก์ชันคลาสสิกใน c สำหรับการกำหนดผังโปรแกรม แต่ก่อน - การเลือกการเรียกใช้ฟังก์ชันที่คุณจะทำตามอาร์กิวเมนต์บรรทัดคำสั่ง)

ใน bash สิ่งเหล่านี้ไม่ใช่ตัวชี้ฟังก์ชัน แต่เป็นตัวแปรที่อ้างถึงชื่อของฟังก์ชันที่คุณใช้ในภายหลัง


คำตอบนี้น่ากลัว ฉันสร้างสคริปต์ทุบตีของตัวอย่างทั้งหมดและเรียกใช้ ฉันยังชอบวิธีที่คุณสร้างผู้สร้าง "เฉพาะการเปลี่ยนแปลง" ซึ่งช่วยได้มาก ย่อหน้าที่สองถึงสุดท้ายของคุณมีการสะกดผิด: "Aabove"
twitchdotcom เฉือน KANJICODER

แก้ไขการพิมพ์ผิด ขอบคุณ
@J

7
ทำไมคุณรวมเข้าด้วยกันถึง()ไม่เริ่มซับเชลล์?
horseyguy


5

คุณไม่สามารถส่งผ่านอะไรไปยังฟังก์ชันอื่นนอกจากสตริง การแทนที่กระบวนการสามารถจัดเรียงของปลอมได้ Bash มีแนวโน้มที่จะเปิด FIFO ค้างไว้จนกว่าคำสั่งจะขยายจนเสร็จสมบูรณ์

นี่เป็นเรื่องโง่อย่างรวดเร็ว

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)

สามารถส่งออกฟังก์ชันได้ แต่ไม่น่าสนใจเท่าที่ปรากฏในตอนแรก ฉันพบว่ามันมีประโยชน์อย่างยิ่งสำหรับการทำให้ฟังก์ชันการดีบักเข้าถึงสคริปต์หรือโปรแกรมอื่น ๆ ที่เรียกใช้สคริปต์

(
    id() {
        "$@"
    }

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

id ยังคงได้รับเฉพาะสตริงที่เป็นชื่อของฟังก์ชัน (นำเข้าโดยอัตโนมัติจากการทำให้เป็นอนุกรมในสภาพแวดล้อม) และ args

ความคิดเห็นของ Pumbaa80 สำหรับคำตอบอื่นก็ดีเช่นกัน ( eval $(declare -F "$1")) แต่มีประโยชน์สำหรับอาร์เรย์เป็นหลักไม่ใช่ฟังก์ชันเนื่องจากเป็นแบบโกลบอลเสมอ หากคุณต้องเรียกใช้สิ่งนี้ภายในฟังก์ชันสิ่งที่ต้องทำคือกำหนดค่าใหม่ดังนั้นจึงไม่มีผลใด ๆ ไม่สามารถใช้เพื่อสร้างการปิดหรือฟังก์ชันบางส่วนหรือ "อินสแตนซ์ของฟังก์ชัน" ขึ้นอยู่กับสิ่งที่เกิดขึ้นเพื่อผูกไว้ในขอบเขตปัจจุบัน ที่ดีที่สุดนี้สามารถนำมาใช้ในการจัดเก็บที่มีความละเอียดฟังก์ชั่นในสายที่ได้รับการนิยามใหม่อื่น ๆ - แต่ฟังก์ชั่นเหล่านั้นเท่านั้นที่สามารถ hardcoded เว้นแต่ของหลักสูตรที่evalจะใช้

โดยทั่วไปจะใช้ Bash แบบนี้ไม่ได้


2

แนวทางที่ดีกว่าคือการใช้ตัวแปรท้องถิ่นในฟังก์ชันของคุณ ปัญหาจะกลายเป็นว่าคุณจะได้รับผลลัพธ์ไปยังผู้โทรอย่างไร กลไกหนึ่งคือการใช้การทดแทนคำสั่ง:

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

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

ผลลัพธ์คือผลลัพธ์ไปยัง stdout และผู้เรียกใช้การแทนที่คำสั่งเพื่อจับค่าในตัวแปร จากนั้นสามารถใช้ตัวแปรได้ตามต้องการ


1

คุณควรมีบางอย่างตามแนวของ:

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

จากนั้นคุณสามารถโทร around x


-1

การประเมินน่าจะเป็นวิธีเดียวที่จะทำให้สำเร็จ ข้อเสียที่แท้จริงเพียงอย่างเดียวคือด้านความปลอดภัยเนื่องจากคุณต้องตรวจสอบให้แน่ใจว่าไม่มีการส่งผ่านสิ่งที่เป็นอันตรายและมีเพียงฟังก์ชันที่คุณต้องการเรียกใช้เท่านั้น (พร้อมกับตรวจสอบว่าไม่มีอักขระที่น่ารังเกียจเช่น ';' ในนั้นด้วย)

ดังนั้นหากคุณเป็นคนเรียกรหัสก็น่าจะเป็นวิธีเดียวที่จะทำได้ โปรดทราบว่ามีรูปแบบการประเมินอื่น ๆ ที่น่าจะใช้ได้ผลเช่นกันกับคำสั่งย่อย ($ () และ ``) แต่ไม่ปลอดภัยกว่าและมีราคาแพงกว่า


eval เป็นวิธีเดียวที่จะทำได้
Wes

1
คุณสามารถตรวจสอบได้อย่างง่ายดายว่าeval $1จะเรียกใช้ฟังก์ชันโดยใช้if declare -F "$1" >/dev/null; then eval $1; fi
user123444555621

2
... หรือดีกว่า:eval $(declare -F "$1")
user123444555621
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.