ฉันสามารถ "ส่งออก" ฟังก์ชั่นในทุบตี?


81
source some_file

some_file:

doit ()
{
  echo doit $1
}
export TEST=true

ถ้าฉันแหล่งsome_fileฟังก์ชั่น "doit" และตัวแปรทดสอบมีอยู่ในบรรทัดคำสั่ง แต่ใช้สคริปต์นี้:

script.sh:

#/bin/sh
echo $TEST
doit test2

จะส่งคืนค่า TEST แต่จะสร้างข้อผิดพลาดเกี่ยวกับฟังก์ชันที่ไม่รู้จัก "doit"

ฉันสามารถ "ส่งออก" ฟังก์ชั่นด้วยหรือฉันต้องมีแหล่ง some_file ใน script.sh เพื่อใช้ฟังก์ชั่นที่นั่นหรือไม่?


2
สรุปคำตอบด้านล่าง (enzotib ถูกต้องสมมติว่าคุณสามารถใช้ทุบตีตามที่ระบุในคำถาม): เปลี่ยน#!/bin/shเป็น#!/bin/bashและหลัง doit() {...}เพียงexport -f doit
ไมเคิล

สำหรับบันทึก: โซลูชันนี้มักจะทำงานเมื่อคุณใช้#!/bin/shเช่นกัน แต่ควรใช้#!/bin/bashเพื่อให้คุณหลีกเลี่ยงปัญหาเมื่อเชลล์เริ่มต้นไม่ได้ถูกทุบตี
Nagel

คำตอบ:


120

ใน Bash คุณสามารถส่งออกนิยามฟังก์ชันไปยังเชลล์ย่อยด้วย

export -f function_name

ตัวอย่างเช่นคุณสามารถลองตัวอย่างง่ายๆนี้:

./script1:

    #!/bin/bash

    myfun() {
        echo "Hello!"
    }

    export -f myfun
    ./script2

./script2:

    #!/bin/bash

    myfun

ถ้าคุณโทร./script1คุณจะเห็นผลลัพธ์Hello! .


16

"การส่งออก" ฟังก์ชั่นที่ใช้export -fสร้างตัวแปรสภาพแวดล้อมด้วยร่างกายของฟังก์ชั่น ลองพิจารณาตัวอย่างนี้:

$ fn(){ echo \'\"\ \ \$; }
$ export -f fn
$ sh -c printenv\ fn
() {  echo \'\"\ \ \$
}

ซึ่งหมายความว่าเฉพาะเชลล์ (แค่ทุบตี?) เท่านั้นที่จะสามารถรับฟังก์ชั่นได้ นอกจากนี้คุณยังสามารถตั้งค่าฟังก์ชั่นได้ด้วยตัวเองเพราะ Bash จะพิจารณา envvars ที่เริ่มต้นด้วย() {ฟังก์ชั่น

$ fn2='() { echo Hi;}' sh -c fn2
Hi
$ fn3='() {' sh -c :
sh: fn3: line 1: syntax error: unexpected end of file
sh: error importing function definition for `fn3'

หากคุณต้องการ "ส่งออก" ตัวแปรนี้ผ่าน SSH คุณต้องใช้ฟังก์ชันเป็นสตริง สามารถทำได้ด้วยตัวเลือกการพิมพ์ ( -p) สำหรับฟังก์ชั่น ( -f) ของdeclareตัวเครื่อง:

$ declare -pf fn
fn () 
{ 
    echo \'\"\ \ \$
}

สิ่งนี้มีประโยชน์มากหากคุณมีรหัสที่ซับซ้อนมากขึ้นซึ่งจำเป็นต้องเรียกใช้งานผ่าน SSH พิจารณาสคริปต์ที่สมมติขึ้นดังต่อไปนี้:

#!/bin/bash
remote_main() {
   local dest="$HOME/destination"

   tar xzv -C "$dest"
   chgrp -R www-data "$dest"
   # Ensure that newly written files have the 'www-data' group too
   find "$dest" -type d -exec chmod g+s {} \;
}
tar cz files/ | ssh user@host "$(declare -pf remote_main); remote_main"

fn2='() { echo Hi;}' sh -c fn2ไม่ได้ผลสำหรับฉัน บน Linux โค้งด้วยshการเป็น v5.0.7 sh: fn2: command not foundทุบตีฉันได้ บน Ubuntu ด้วยกับshการเป็น v0.2.3 sh: 1: fn2: not foundรีบผมได้ shคุณใช้เชลล์รุ่นไหน?
Socowi

ฉันคิดว่าคำตอบของคุณผิด f(){ echo a;}; export -f f; echo "$f"; sh -c 'printenv f; echo "$f"'พิมพ์อะไรเลยสำหรับฉันดังนั้นจึงfไม่ได้ส่งออกเป็นสตริงธรรมดา ฉันทดสอบด้วยชุดค่าผสมทั้งสองที่กล่าวถึงข้างต้น
Socowi

และแม้ว่าฟังก์ชั่นจะถูกส่งออกเป็นสตริงทำไมจึงควรshรันสตริงนั้นเป็นฟังก์ชั่น? ในตัวอย่างของคุณfn2='() { echo Hi;}' sh -c fn2คำสั่งที่fn2กำหนดให้sh คือสตริง"fn2"อย่างแท้จริง shควรค้นหาคำสั่งดังกล่าวใน PATH แต่ไม่ควรดูว่ามีตัวแปรหรือไม่$fn2ให้ขยายตัวแปรนั้นและดำเนินการค่าของฟังก์ชั่น - ฟังดูเหมือนปัญหาด้านความปลอดภัยที่สำคัญสำหรับฉัน แก้ไข:ฉันคิดว่ามัน! พฤติกรรมที่คุณแสดงเป็นข้อบกพร่องด้านความปลอดภัยที่รู้จักในชื่อShellShock / Bashdoorหรือไม่?
Socowi

ด้วย Bash 5.0.7 (Arch Linux) ดูเหมือนว่ามีการเติมคำนำหน้า สิ่งนี้น่าจะเกิดขึ้นในการตอบสนอง ShellShock ตัวอย่าง: fn(){ echo foo; }; export -f fn; env | grep fooผลลัพธ์BASH_FUNC_fn%%=() { echo foo
Lekensteyn

7

อาคาร@ คำตอบ Lekensteyn ของ ...

หากคุณใช้declare -pfมันจะเอาท์พุทฟังก์ชั่นที่กำหนดไว้ก่อนหน้านี้ทั้งหมดในเปลือกปัจจุบันไปยัง STDOUT

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

คำตอบต่อไปนี้จะบรรจุไว้ในตัวแปร จากนั้นเราก็สะท้อนตัวแปรนั้นบวกกับการเรียกใช้ฟังก์ชันที่เราต้องการเรียกใช้ลงในเชลล์ใหม่ที่ถูกสร้างใหม่ในฐานะผู้ใช้ใหม่ เราทำเช่นนี้โดยใช้sudoกับ-u(aka. user) สวิทช์และก็ทำงานทุบตี (ซึ่งจะได้รับประปา STDOUT เป็น input ในการทำงาน)

เมื่อเรารู้ว่าเรากำลังเปลี่ยนจาก Bash shell ไปเป็น Bash shell เรารู้ว่า Bash จะแปลความหมายของฟังก์ชั่นที่กำหนดไว้ก่อนหน้าอย่างถูกต้อง ไวยากรณ์ควรใช้ได้ตราบใดที่เราย้ายระหว่างหนึ่ง Bash เชลล์ของเวอร์ชันเดียวกันเป็น Bash เชลล์ใหม่ของเวอร์ชันเดียวกัน

YMMV ถ้าคุณย้ายระหว่างเชลล์ที่ต่างกันหรือระหว่างระบบที่อาจมี Bash เวอร์ชั่นต่างกัน

#!/bin/bash
foo() {
  echo "hello from `whoami`"
}

FUNCTIONS=`declare -pf`; echo "$FUNCTIONS ; foo" | sudo -u otheruser bash
# $./test.sh
# hello from otheruser

5

คุณไม่สามารถส่งออกฟังก์ชั่นไม่ใช่ในแบบที่คุณอธิบาย เชลล์จะโหลด~/.bashrcไฟล์เมื่อเริ่มต้นของเชลล์แบบโต้ตอบเท่านั้น (ค้นหา "การเรียกใช้" ในbash manpage )

สิ่งที่คุณสามารถทำได้คือสร้าง "ไลบรารี่" ซึ่งโหลดเมื่อคุณเริ่มโปรแกรม:

source "$HOME/lib/somefile"

และวางฟังก์ชั่นและการตั้งค่าแบบไม่โต้ตอบของคุณไว้ที่นั่น


ดังนั้นฉันต้องเริ่มต้น subshell ด้วยพารามิเตอร์ "login" (แยกวิเคราะห์ ~ / .profile) หรือซอร์สไฟล์นั้น
นิลส์

หากมองอย่างใกล้ชิดยิ่งขึ้นบนเชลล์ที่ไม่มีการโต้ตอบคุณสามารถตั้งค่าBASH_ENVตัวแปรสภาพแวดล้อมให้some_fileคุณมีอยู่แล้วและมันจะถูกเรียกใช้ มันจะง่ายพอที่จะค้นพบสิ่งนั้น:echo echo foobar > /tmp/foobar; BASH_ENV=/tmp/foobar $SHELL -c :
Arcege

2

eval "$(declare -F | sed -e 's/-f /-fx /')"จะส่งออกฟังก์ชั่นทั้งหมด

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

ตัวอย่าง:

eval "$(declare -F | sed -e 's/-f /-fx /')"
export SOME IMPORTANT VARIABLES AND PASSWORDS
bash -i

1

ฟังก์ชั่นจะไม่ถูกส่งออกไปยังกระบวนการย่อย นี่คือเหตุผลที่มีไฟล์ชื่อ. kshrc หรือ. bashrc: ในการกำหนดฟังก์ชั่นที่ shoiuld ให้บริการใน subshells ด้วย

หากเรียกใช้สคริปต์สคริปต์. * shrc โดยทั่วไปจะไม่ได้รับที่มา . ~/.kshrcคุณจะต้องรหัสที่ชัดเจนเหมือนใน


ดังนั้น ~ root / .bashrc อาจเป็นตัวเลือกในกรณีของฉันเนื่องจากสคริปต์รันเป็นรูท ขอบคุณสำหรับคำใบ้นั้น
นิลส์

หากใช้ไฟล์. * shrc ต้องแน่ใจว่าพวกเขาไม่บังคับพฤติกรรมแบบโต้ตอบ (เช่นนามแฝงโง่rm=rm -i)
ktf

0

ฉันยังใหม่กับ Linux แต่คุณสามารถลองได้ ในไฟล์บางไฟล์ลองเรียกมันว่า 'tmp / general' ที่คุณสร้างฟังก์ชั่น:

func1(){
   echo "func from general"
}

ในเชลล์สคริปต์ของคุณเพิ่ม:

. /tmp/general

และเรียกใช้:

func1

คุณจะได้รับบนหน้าจอ: func from general.


0
declare -x -f NAME

ข้อมูลเพิ่มเติม

- จำกัด การกระทำหรือการแสดงชื่อฟังก์ชั่นและคำจำกัดความ
-x เพื่อให้ NAME ส่งออก
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.