ขอบเขตตัวแปร Bash


104

โปรดอธิบายให้ฉันทราบว่าเหตุใดข้อความสุดท้ายechoจึงว่างเปล่า? ฉันคาดว่าXCODEจะเพิ่มขึ้นใน while loop เป็นค่า 1:

#!/bin/bash
OUTPUT="name1 ip ip status" # normally output of another command with multi line output

if [ -z "$OUTPUT" ]
then
        echo "Status WARN: No messages from SMcli"
        exit $STATE_WARNING
else
        echo "$OUTPUT"|while read NAME IP1 IP2 STATUS
        do
                if [ "$STATUS" != "Optimal" ]
                then
                        echo "CRIT: $NAME - $STATUS"
                        echo $((++XCODE))
                else
                        echo "OK: $NAME - $STATUS"
                fi
        done
fi

echo $XCODE

ฉันได้ลองใช้คำสั่งต่อไปนี้แทน++XCODEวิธีการ

XCODE=`expr $XCODE + 1`

และมันจะไม่พิมพ์นอกคำสั่ง while ด้วย ฉันคิดว่าฉันขาดบางอย่างเกี่ยวกับขอบเขตตัวแปรที่นี่ แต่หน้าคนเก่าไม่แสดงให้ฉันเห็น


คุณเริ่มต้น XCODE ไปยังสิ่งที่สามารถเพิ่มได้ที่ไหน?
Paul Tomblin

ฉันได้พยายามใส่ "XCODE = 0" ที่ด้านบนของโค้ดนอกคำสั่ง while
Matt P

ถ้าไม่มีไม้ค้ำยันก็ใช้ได้สำหรับฉัน #! / bin / bash สำหรับ i in 1 2 3 4 5; ทำ echo $ ((++ XCODE)) เสร็จแล้ว echo "fin:" $ XCODE ฉันคิดว่าปัญหาของคุณไม่เกี่ยวข้องกับการกำหนดขอบเขตตัวแปรและทุกสิ่งที่เกี่ยวข้องกับสิ่งที่เกิดขึ้นในขณะนั้น
Paul Tomblin

ตกลง .. ดูเหมือนว่ามันจะเกี่ยวข้องกับลูป "while read" หรือไม่?
Matt P

มีคำถามที่พบบ่อยเกี่ยวกับ Bash: mywiki.wooledge.org/BashFAQ/024
Fabio กล่าว Reinstate Monica

คำตอบ:


117

เนื่องจากคุณกำลังไพพ์เข้าไปในลูป while จึงมีการสร้างเชลล์ย่อยขึ้นเพื่อเรียกใช้ลูป while

ตอนนี้โปรเซสลูกนี้มีสำเนาสภาพแวดล้อมของตัวเองและไม่สามารถส่งผ่านตัวแปรกลับไปยังพาเรนต์ได้ (เช่นเดียวกับในกระบวนการยูนิกซ์ใด ๆ )

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

http://tldp.org/LDP/abs/html/subshells.html#SUBSHELL


2
นี่เป็นเพียงคำตอบของปัญหาที่ดูเหมือนสุ่มจำนวนมากที่ฉันพบด้วยการเขียนสคริปต์ทุบตี
Daniel Agans

คำตอบที่สมบูรณ์แบบนี้ทำให้ฉันเสียใจมากและอธิบายพฤติกรรมแปลก ๆ ในระบบ CI ของเรา
KayCee

108

ปัญหาคือกระบวนการที่รวมกับไปป์จะดำเนินการใน subshells (ดังนั้นจึงมีสภาพแวดล้อมของตัวเอง) สิ่งที่เกิดขึ้นภายในwhileไม่ส่งผลกระทบต่อสิ่งใดนอกท่อ

ตัวอย่างเฉพาะของคุณสามารถแก้ไขได้โดยเขียนไปป์ใหม่

while ... do ... done <<< "$OUTPUT"

หรือบางที

while ... do ... done < <(echo "$OUTPUT")

32
สำหรับผู้ที่กำลังดูสิ่งนี้สับสนว่าไวยากรณ์ <() ทั้งหมดคืออะไร (เหมือนที่ฉันเป็น) เรียกว่า "การทดแทนกระบวนการ" และการใช้งานเฉพาะรายละเอียดด้านบนสามารถดูได้ที่นี่: mywiki.wooledge.org/ProcessSubstitution
Ross Aiken

2
การทดแทนกระบวนการเป็นสิ่งที่ทุกคนควรใช้เป็นประจำ! มีประโยชน์สุด ๆ ฉันทำอะไรบางอย่างเช่นvimdiff <(grep WARN log.1 | sort | uniq) <(grep WARN log.2 | sort | uniq)ทุกวัน พิจารณาว่าคุณสามารถใช้หลายไฟล์พร้อมกันและปฏิบัติเหมือนไฟล์ ...
Bruno Bronosky

8

สิ่งนี้ควรใช้งานได้เช่นกัน (เนื่องจาก echo และในขณะที่อยู่ใน subshell เดียวกัน):

#!/bin/bash
cat /tmp/randomFile | (while read line
do
    LINE="$LINE $line"
done && echo $LINE )

3

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

#!/bin/bash
cat /some/file | while read line
do
  var="abc"
  echo $var | xsel -i -p  # redirect stdin to the X primary selection
done
var=$(xsel -o -p)  # redirect back to stdout
echo $var

แก้ไข: ที่นี่ xsel เป็นข้อกำหนด (ติดตั้ง) หรือคุณสามารถใช้ xclip: xclip -i -selection clipboard แทน xsel -i -p


ฉันได้รับข้อผิดพลาด: ./scraper.sh: บรรทัด 111: xsel: command not found ./scraper.sh: line 114: xsel: command not found
3kstc

@ 3kstc ชัดติดตั้ง xsel นอกจากนี้คุณสามารถใช้ xclip ได้ แต่การใช้งานแตกต่างกันเล็กน้อย ประเด็นหลักที่นี่: อันดับแรกคุณใส่เอาต์พุตลงในคลิปบอร์ด (3 อันใน linux), อันดับ 2 - คุณคว้ามันจากที่นั่นและส่งไปที่ stdout
Rammix

1
 #!/bin/bash
 OUTPUT="name1 ip ip status"
+export XCODE=0;
 if [ -z "$OUTPUT" ]
----

                     echo "CRIT: $NAME - $STATUS"
-                    echo $((++XCODE))
+                    export XCODE=$(( $XCODE + 1 ))
             else

echo $XCODE

ดูว่าการเปลี่ยนแปลงเหล่านั้นช่วยได้หรือไม่


เมื่อทำเช่นนี้ตอนนี้ฉันจะได้รับ "0" เพื่อพิมพ์สำหรับคำสั่ง echo สุดท้าย อย่างไรก็ตามฉันคาดว่าค่าจะเป็น 1 ไม่ใช่ศูนย์ นอกจากนี้ทำไมต้องใช้การส่งออก? ฉันคิดว่าบังคับให้เข้าสู่สิ่งแวดล้อม?
Matt P

0

อีกทางเลือกหนึ่งคือการส่งออกผลลัพธ์ไปยังไฟล์จากเชลล์ย่อยแล้วอ่านในพาเรนต์เชลล์ สิ่งที่ต้องการ

#!/bin/bash
EXPORTFILE=/tmp/exportfile${RANDOM}
cat /tmp/randomFile | while read line
do
    LINE="$LINE $line"
    echo $LINE > $EXPORTFILE
done
LINE=$(cat $EXPORTFILE)

0

ฉันได้รับสิ่งนี้เมื่อฉันสร้าง du ตัวน้อยของตัวเอง:

ls -l | sed '/total/d ; s/  */\t/g' | cut -f 5 | 
( SUM=0; while read SIZE; do SUM=$(($SUM+$SIZE)); done; echo "$(($SUM/1024/1024/1024))GB" )

ประเด็นคือฉันสร้าง subshell ด้วย () ที่มีตัวแปร SUM ของฉันและ while แต่ฉันไพพ์เข้าไปในทั้ง () แทนที่จะเป็น while เองซึ่งจะหลีกเลี่ยง gotcha

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