การตั้งค่าตัวแปรสภาพแวดล้อมก่อนที่คำสั่งใน Bash จะไม่ทำงานสำหรับคำสั่งที่สองในไพพ์


351

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

FOO=bar somecommand someargs

มันใช้งานได้ ... มันไม่ทำงานเมื่อคุณเปลี่ยนตัวแปร LC_ * (ซึ่งดูเหมือนว่าจะส่งผลกระทบต่อคำสั่ง แต่ไม่ใช่ข้อโต้แย้งตัวอย่างเช่น '[az]' ช่วงถ่าน) หรือเมื่อไพพ์เอาต์พุตไปยังคำสั่งอื่น:

FOO=bar somecommand someargs | somecommand2  # somecommand2 is unaware of FOO

ฉันสามารถเติม somecommand2 ด้วย "FOO = bar" ได้ด้วยซึ่งใช้งานได้ แต่เพิ่มการซ้ำซ้อนที่ไม่ต้องการและไม่ช่วยอาร์กิวเมนต์ที่ตีความขึ้นอยู่กับตัวแปร (เช่น '[az]')

ดังนั้นวิธีที่ดีในการทำเช่นนี้ในบรรทัดเดียวคืออะไร?

ฉันกำลังคิดบางอย่างตามคำสั่งของ:

FOO=bar (somecommand someargs | somecommand2)  # Doesn't actually work

ฉันได้รับคำตอบที่ดีมากมาย! เป้าหมายคือเพื่อให้เป็นหนึ่งซับโดยเฉพาะอย่างยิ่งโดยไม่ต้องใช้ "ส่งออก" วิธีการใช้การเรียกไปยัง Bash นั้นดีที่สุดโดยรวมแม้ว่ารุ่นที่ใช้ parenth กับ "export" ในนั้นมีขนาดเล็กกว่าเล็กน้อย วิธีการใช้การเปลี่ยนเส้นทางมากกว่าไปป์ก็น่าสนใจเช่นกัน


1
(T=$(date) echo $T)จะทำงาน
vp_arth

ในบริบทของการข้ามแพลตฟอร์ม (รวม. Windows) สคริปต์หรือโครงการ NPM-based (js หรืออื่น ๆ ) คุณอาจต้องการที่จะดูที่โมดูลข้าม env
Frank Nocke

5
ฉันหวังว่าหนึ่งในคำตอบจะอธิบายว่าทำไมงานประเภทนี้เพียงอย่างเดียวนั่นคือเหตุใดจึงไม่เทียบเท่ากับการส่งออกตัวแปรก่อนการโทร
Brecht Machiels

4
สาเหตุที่อธิบายไว้ที่นี่: stackoverflow.com/questions/13998075/…
Brecht Machiels

คำตอบ:


317
FOO=bar bash -c 'somecommand someargs | somecommand2'

2
สิ่งนี้เป็นไปตามเกณฑ์ของฉัน (หนึ่งซับโดยไม่จำเป็นต้อง "ส่งออก") ... ฉันคิดว่าไม่มีวิธีที่จะทำเช่นนี้โดยไม่เรียก "bash -c" (เช่นการใช้วงเล็บที่สร้างสรรค์)?
MartyMacGyver

1
@MartyMacGyver: ไม่มีที่ฉันสามารถคิดได้ มันจะไม่ทำงานกับวงเล็บปีกกาอย่างใดอย่างหนึ่ง
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

7
โปรดทราบว่าหากคุณต้องการเรียกใช้งานsomecommandเป็น sudo คุณต้องผ่าน sudo -Eค่าสถานะเพื่อส่งผ่านตัวแปร เพราะตัวแปรสามารถแนะนำช่องโหว่ได้ stackoverflow.com/a/8633575/1695680
ThorSummoner

11
โปรดทราบว่าหากคำสั่งของคุณมีเครื่องหมายคำพูดสองระดับแล้ววิธีนี้จะไม่น่าพึงพอใจอย่างยิ่งเนื่องจากคำพูดของนรก ในสถานการณ์นั้นการส่งออกใน subshell ดีกว่ามาก
Pushpendre

แปลก: บน OSX FOO_X=foox bash -c 'echo $FOO_X'ทำงานได้ตามที่คาดหวัง แต่ด้วยชื่อ var ที่เฉพาะเจาะจงมันล้มเหลว: DYLD_X=foox bash -c 'echo $DYLD_X'echos blank ทำงานทั้งสองใช้evalแทนbash -c
mwag

209

วิธีการเกี่ยวกับการส่งออกตัวแปร แต่ภายใน subshell เท่านั้น:

(export FOO=bar && somecommand someargs | somecommand2)

Keith มีจุดเพื่อดำเนินการคำสั่งอย่างไม่มีเงื่อนไขทำสิ่งนี้:

(export FOO=bar; somecommand someargs | somecommand2)

15
ฉันต้องการใช้;มากกว่า&&; ไม่มีทางที่export FOO=barจะล้มเหลว
Keith Thompson

1
@MartyMacGyver: &&ดำเนินการคำสั่งด้านซ้ายจากนั้นดำเนินการคำสั่งที่ถูกต้องเฉพาะในกรณีที่คำสั่งด้านซ้ายประสบความสำเร็จ ;รันคำสั่งทั้งสองอย่างไม่มีเงื่อนไข ของ Windows ชุด ( cmd.exe) เทียบเท่ากับการมี; &
Keith Thompson

2
ใน zsh ฉันดูเหมือนจะไม่จำเป็นต้องส่งออกสำหรับรุ่นนี้: อัตราผลตอบแทน(FOO=XXX ; echo FOO=$FOO) ; echo FOO=$FOO FOO=XXX\nFOO=\n
rampion

3
@PopePoopinpants: ทำไมไม่ใช้source(aka .) ในกรณีนั้น นอกจากนี้ไม่ควรใช้ backticks อีกต่อไปในวันนี้และนี่คือหนึ่งในเหตุผลที่การใช้$(command)คือ waaaaay ปลอดภัยยิ่งขึ้น
0xC0000022L

3
เรียบง่าย แต่สง่างาม และฉันชอบคำตอบของคุณดีกว่าคำตอบที่ยอมรับเพราะมันจะเริ่มเชลล์ย่อยเท่ากับอันปัจจุบันของฉัน (ซึ่งอาจไม่ใช่bashแต่อาจเป็นอย่างอื่นเช่นdash) และฉันไม่พบปัญหาใด ๆ หากฉันต้องใช้เครื่องหมายคำพูด ภายในคำสั่ง args ( someargs)
Mecki

45

คุณยังสามารถใช้eval:

FOO=bar eval 'somecommand someargs | somecommand2'

เนื่องจากคำตอบevalนี้ดูเหมือนจะไม่ทำให้ทุกคนพอใจโปรดให้ฉันอธิบายบางสิ่ง: เมื่อใช้เป็นลายลักษณ์อักษรด้วยเครื่องหมายคำพูดเดียวมันจึงปลอดภัยอย่างสมบูรณ์ มันดีเพราะมันจะไม่เริ่มกระบวนการภายนอก (เช่นคำตอบที่ยอมรับ) และจะไม่รันคำสั่งใน subshell พิเศษ (เช่นคำตอบอื่น ๆ )

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

mypipe() {
    somecommand someargs | somecommand2
}

และดำเนินการกับตัวแปรสภาพแวดล้อมของคุณเช่นนี้:

FOO=bar mypipe

7
@Alfe: คุณลงคะแนนด้วยคำตอบที่ยอมรับหรือไม่? เพราะการจัดแสดงนิทรรศการเดียวกัน“ปัญหา” evalเป็น
gniourf_gniourf

10
@Alfe: น่าเสียดายที่ฉันไม่เห็นด้วยกับคำวิจารณ์ของคุณ คำสั่งนี้ปลอดภัยอย่างสมบูรณ์ จริงๆคุณเสียงเหมือนคนที่เคยอ่านevalเป็นความชั่วร้ายevalไม่เข้าใจสิ่งที่ชั่วร้ายเกี่ยวกับ และบางทีคุณอาจไม่เข้าใจคำตอบนี้จริง ๆ (และไม่มีอะไรผิดปกติกับมัน) ในระดับเดียวกันคุณจะบอกว่าlsไม่ดีเพราะ for file in $(ls)แย่ใช่ไหม (และใช่คุณไม่ได้ลงคะแนนคำตอบที่ยอมรับและคุณไม่ได้แสดงความคิดเห็นด้วย) ดังนั้นเป็นสถานที่ที่แปลกและไร้เหตุผลในบางครั้ง
gniourf_gniourf

7
@Alfe: เมื่อฉันพูดจริงๆคุณเสียงเหมือนคนที่เคยอ่านevalเป็นความชั่วร้ายไม่เข้าใจสิ่งที่ชั่วร้ายเกี่ยวกับeval,ผมหมายถึงประโยคของคุณ: คำตอบนี้ขาดคำเตือนและคำอธิบายที่จำเป็นทั้งหมดเมื่อพูดถึง eval evalไม่เลวหรืออันตราย bash -cไม่เกิน
gniourf_gniourf

1
โหวตความคิดเห็นที่ให้ไว้ @Alfe แปลว่าคำตอบที่ยอมรับนั้นปลอดภัยกว่าอย่างใด evalสิ่งที่จะได้รับประโยชน์มากขึ้นจะได้รับสำหรับคุณที่จะอธิบายสิ่งที่คุณเชื่อว่าจะไม่ปลอดภัยเกี่ยวกับการใช้ของ ในคำตอบที่ให้ args ได้รับการยกมาป้องกันเดียวจากการขยายตัวของตัวแปรดังนั้นฉันไม่เห็นปัญหากับคำตอบ
Brett Ryan

ฉันลบความคิดเห็นของฉันเพื่อเน้นความกังวลของฉันในความคิดเห็นใหม่: evalเป็นปัญหาด้านความปลอดภัยโดยทั่วไป (เหมือนbash -cแต่ชัดเจนน้อยกว่า) ดังนั้นควรพูดถึงอันตรายในคำตอบที่เสนอการใช้งาน ผู้ใช้ที่ไม่เอาใจใส่อาจใช้คำตอบ ( FOO=bar eval …) และนำไปใช้กับสถานการณ์ของพวกเขาเพื่อให้เกิดปัญหา แต่เห็นได้ชัดว่ามันสำคัญสำหรับผู้ตอบที่จะคิดออกว่าฉันลงคะแนนเขาและ / หรือคำตอบอื่น ๆ มากกว่าที่จะปรับปรุงอะไร อย่างที่ฉันเขียนไว้ก่อนหน้าความยุติธรรมไม่ควรเป็นปัญหาหลัก เป็นไม่เลวร้ายยิ่งกว่าใด ๆ คำตอบให้คนอื่น ๆ ก็คือไม่คำนึงถึง
Alfe

12

envใช้

ตัวอย่างเช่นenv FOO=BAR command. โปรดทราบว่าตัวแปรสภาพแวดล้อมจะถูกเรียกคืน / ไม่เปลี่ยนแปลงอีกครั้งเมื่อcommandดำเนินการเสร็จสิ้น

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

$ export FOO=BAR
$ env FOO=FUBAR bash -c 'echo $FOO'
FUBAR
$ echo $FOO
BAR

-5

ใช้เชลล์สคริปต์:

#!/bin/bash
# myscript
FOO=bar
somecommand someargs | somecommand2

> ./myscript

13
คุณยังคงต้องexport; มิฉะนั้น$FOOจะเป็นตัวแปรเปลือกไม่ได้เป็นตัวแปรสภาพแวดล้อมและดังนั้นจึงไม่สามารถมองเห็นหรือsomecommand somecommand2
Keith Thompson

มันใช้งานได้ แต่มันเอาชนะวัตถุประสงค์ของการมีคำสั่งแบบบรรทัดเดียว (ฉันพยายามเรียนรู้วิธีที่สร้างสรรค์มากขึ้นเพื่อหลีกเลี่ยง multi-liners และ / หรือสคริปต์สำหรับกรณีที่ค่อนข้างง่าย) และสิ่งที่ @Keith พูดถึงแม้ว่าอย่างน้อยการส่งออกจะยังคงอยู่ในขอบเขตของสคริปต์
MartyMacGyver
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.