ตัวแปรสภาพแวดล้อมไม่ได้ถูกตั้งค่าเมื่อฟังก์ชั่นของฉันถูกเรียกในไปป์ไลน์


10

ฉันมีฟังก์ชันแบบเรียกซ้ำต่อไปนี้เพื่อตั้งค่าตัวแปรสภาพแวดล้อม:

function par_set {
  PAR=$1
  VAL=$2
  if [ "" != "$1" ]
  then
    export ${PAR}=${VAL}
    echo ${PAR}=${VAL}
    shift
    shift
    par_set $*
  fi
}

ถ้าฉันเรียกมันด้วยตัวมันเองมันจะตั้งค่าตัวแปรและ echoes เป็น stdout:

$ par_set FN WORKS
FN=WORKS
$ echo "FN = "$FN
FN = WORKS

การเปลี่ยนเส้นทาง stdout ไปยังไฟล์ยังใช้งานได้:

$ par_set REDIR WORKS > out
cat out
REDIR=WORKS
$ echo "REDIR = "$REDIR
REDIR = WORKS

แต่ถ้าฉันไพพ์ stdout ไปยังคำสั่งอื่นตัวแปรไม่ได้รับการตั้งค่า:

$ par_set PIPE FAILS |sed -e's/FAILS/BARFS/'
PIPE=BARFS
$ echo "PIPE = "$PIPE
PIPE =

เหตุใดท่อจึงป้องกันไม่ให้ฟังก์ชันส่งออกตัวแปร มีวิธีแก้ไขโดยไม่หันไปใช้ไฟล์ temp หรือไพพ์ที่มีชื่อหรือไม่?

การแก้ไข:

รหัสการทำงานต้องขอบคุณ Gilles:

par_set $(echo $*|tr '=' ' ') > >(sed -e's/^/  /' >> ${LOG})

สิ่งนี้ทำให้สคริปต์ถูกเรียกอย่างนี้:

$ . ./script.sh PROCESS_SUB ROCKS PIPELINES=NOGOOD
$ echo $PROCESS_SUB
ROCKS
$ echo $PIPELINES
NOGOOD
$ cat log
7:20140606155622162731431:script.sh:29581:Parse Command Line parameters.  Params must be in matched pairs separated by one or more '=' or ' '.
  PROCESS_SUB=ROCKS
  PIPELINES=NOGOOD

โครงการโฮสต์บน bitbucket https://bitbucket.org/adalby/monitor-bashหากสนใจในรหัสเต็ม

คำตอบ:


8

ส่วนหนึ่งของท่อแต่ละ (เช่นด้านข้างของท่อแต่ละคน) ทำงานในกระบวนการที่แยกต่างหาก (เรียกว่า subshell เมื่อเปลือกส้อมกระบวนการย่อยเพื่อใช้เป็นส่วนหนึ่งของสคริปต์) ในpar_set PIPE FAILS |sed -e's/FAILS/BARFS/'ที่PIPEตัวแปรตั้งอยู่ในกระบวนการย่อยที่ดำเนินการด้านซ้ายมือของท่อ การเปลี่ยนแปลงนี้ไม่ได้สะท้อนให้เห็นในกระบวนการหลัก (ตัวแปรสภาพแวดล้อมไม่ได้ถ่ายโอนระหว่างกระบวนการ แต่จะถูกสืบทอดโดยกระบวนการย่อยเท่านั้น

ด้านซ้ายของไปป์จะทำงานใน subshell เสมอ เชลล์บางตัว (ATT ksh, zsh) เรียกใช้ทางด้านขวาในเชลล์พาเรนต์ ส่วนใหญ่เรียกใช้ทางด้านขวามือใน subshell

หากคุณต้องการทั้งการเปลี่ยนเส้นทางการส่งออกของส่วนหนึ่งของสคริปต์และเรียกว่าเป็นส่วนหนึ่งในเปลือกปกครองใน ksh / ทุบตี / zsh คุณสามารถใช้ทดแทนกระบวนการ

par_set PROCESS SUBSTITUTION > >(sed s/ION/ED/)

ด้วย POSIX เชลล์ใด ๆ คุณสามารถเปลี่ยนทิศทางเอาต์พุตไปยังไพพ์ที่มีชื่อ

mkfifo f
<f grep NAMED= &
par_set NAMED PIPE >f

โอ้และคุณกำลังขาดหายไปคำพูดรอบแทนตัวแปรpar_set name 'value with spaces' star '*'แบ่งรหัสของคุณในสิ่งที่ต้องการ

export "${PAR}=${VAL}"

par_set "$@"

ทดแทนกระบวนการสำหรับการชนะ! ฉันรู้ว่าฉันสามารถใช้ไฟล์ที่มีชื่อว่า pipe หรือ temp ได้ แต่ไฟล์เหล่านั้นน่าเกลียดมีการทำงานพร้อมกันที่ไม่ดีและทิ้งความยุ่งเหยิงไว้ถ้าสคริปต์นั้นตาย (กับดักช่วยในไฟล์สุดท้าย) สิ่งที่มีพื้นที่โดยเจตนา โดยการประชุมตัวแปรที่ส่งผ่านบนบรรทัดคำสั่งจะอยู่ในคู่ชื่อ / ค่าและคั่นด้วย '=' และ / หรือ ''
Andrew

3

สิ่งนี้ไม่ทำงานเพราะแต่ละข้างของไพพ์ทำงานใน subshell ในbashและตัวแปรที่ตั้งใน subshell นั้นอยู่ในโลคอลกับ subshell นั้น

ปรับปรุง:

ดูเหมือนว่าจะส่งผ่านตัวแปรจากพาเรนต์ไปยังเชลล์ลูกได้ง่าย แต่ก็ยากที่จะทำอย่างอื่น วิธีแก้ไขปัญหาบางอย่างมีชื่อไปป์ไฟล์ temp การเขียนไปยัง stdout และการอ่านในพาเรนต์ ฯลฯ

อ้างอิงบางส่วน:

http://mywiki.wooledge.org/BashFAQ/024
https://stackoverflow.com/q/15541321/3565972
https://stackoverflow.com/a/15383353/3565972
http://forums.opensuse.org/showthread .php / 458979-วิธีการส่งออกตัวแปรใน subshell กลับออกไปยังผู้ปกครอง


ฉันคิดว่ามันเป็นไปได้ แต่ฟังก์ชั่นควรทำงานในเชลล์ปัจจุบัน ฉันทดสอบโดยเพิ่ม "echo $$" ลงในฟังก์ชัน par_set STILL FAILS | sed -e "s / ^ / sedpid = $$, fnpid = /" เอาต์พุต sedpid = 15957, fnpid = 15957
แอนดรูว์

@Andrew ดูstackoverflow.com/a/20726041/3565972นี้ เห็นได้ชัดว่า$$เหมือนกันสำหรับทั้งเปลือกลูกและเปลือก คุณสามารถใช้$BASHPIDเพื่อรับ pid ของ subshell เมื่อฉันecho $$ $BASHPIDอยู่ภายในpar_setฉันจะได้รับ pids ที่แตกต่างกัน
savanto

@Andrew ยังคงพยายามหาวิธีแก้ปัญหา แต่ล้มเหลว! =)
savanto

@ Savanto-ขอบคุณ ฉันไม่ทราบว่าเกี่ยวกับ $$ vs $ BASHPID หรือไพพ์บังคับให้ subshells
Andrew

0

คุณชี้ให้เห็น subshells - ที่สามารถได้รอบกับ finagling บางอย่างในเปลือกนอกท่อ - แต่ส่วนที่ยากของปัญหาที่มีจะทำอย่างไรกับท่อที่เห็นพ้องด้วย

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

{ sleep 1 ; echo $((f=1+2)) >&2 ; } | echo $((f))
###OUTPUT
0
...
3

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

ฉันไม่เข้าใจจริงๆว่าหน้าที่ของคุณคืออะไร - มีจุดประสงค์อะไรบ้างที่ยังไม่ได้ทำexport? หรือเพียงแค่var=val? ตัวอย่างเช่นต่อไปนี้เป็นขั้นตอนเดียวกันเกือบทั้งหมด :

pipeline() { 
    { sleep 1
      echo "f=$((f=f+1+2))" >&3
    } | echo $((f)) >&2
} 3>&1

f=4 pipeline

###OUTPUT

4
...
f=7

และด้วยexport:

export $(f=4 pipeline) ; pipeline

###OUTPUT:

4
7
...
f=10

ดังนั้นสิ่งที่คุณสามารถทำงานได้:

par_set $(echo PIPE FAILS | 
    sed 's/FAIL/WORK/;/./w /path/to/log')

ซึ่งจะเข้าสู่ระบบไปยังแฟ้มของการส่งออกและส่งมอบให้กับการทำงานของคุณเป็นเปลือกแยกsed"$@"

หรืออีกทางหนึ่ง:

$ export $(echo PIPE FAILS | sed 's/ FAIL/=WORK/')
$ par_set $PIPE TWICE_REMOVED
$ echo "WORKS = "$WORKS
WORKS = TWICE_REMOVED

ถ้าฉันจะเขียนฟังก์ชั่นของคุณมันอาจจะเป็นแบบนี้:

_env_eval() { 
    while ${2+:} false ; do
       ${1:+export} ${1%%["${IFS}"]*}="${2}" || :
       shift 2
    done
}

นั่นเป็นเรื่องจริง แต่ไม่เกี่ยวข้อง: แอนดรูว์พยายามใช้ตัวแปรหลังจากไพพ์ไลน์สิ้นสุดลงไม่ใช่ที่อีกด้านหนึ่งของไพพ์
Gilles 'หยุดความชั่วร้าย'

@Gilles - เขาทำได้ดี |pipeline | sh
mikeserv

@Gilles - shจริงคุณไม่จำเป็นต้อง exportเขาได้ใช้
mikeserv

เป้าหมายโดยรวมคือสคริปต์ตรวจสอบระบบ ต้องการที่จะสามารถเรียกพวกเขาโต้ตอบจากสคริปต์อื่น ๆ หรือจาก cron ต้องการให้สามารถส่งพารามิเตอร์โดยการตั้งค่า env vars ส่งผ่านบรรทัดคำสั่งหรือไฟล์กำหนดค่า มีฟังก์ชันเพื่อรับอินพุตจากแหล่งหนึ่งแหล่งขึ้นไปและตั้งค่าสภาพแวดล้อมให้ถูกต้อง อย่างไรก็ตามฉันยังต้องการที่จะสามารถเลือกบันทึกผลลัพธ์ (หลังจากทำงานผ่าน sed เพื่อจัดรูปแบบ) ดังนั้นจึงต้องไปป์
แอนดรู

1
@ mikeserv- ขอบคุณสำหรับความคิดเห็นทั้งหมด ฉันไปกับข้อเสนอแนะ Gilles ของการทดแทนกระบวนการ แต่ต้องเถียงเพราะรหัสของฉันทำให้ฉันเป็นโปรแกรมเมอร์ที่ดีขึ้น
Andrew
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.