จะผ่าน 2> / dev / null เป็นตัวแปรได้อย่างไร


13

ฉันมีรหัสนี้ใช้งานได้:

# Hide irrelevant errors so chrome doesn't email us in cron
if [[ $fCron == true ]] ; then
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName" 2>/dev/null
else
    # Get silly error messages when running from terminal
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
fi

ถ้าฉันพยายามย่อให้สั้นลงเช่นนี้:

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
[[ $fCron == true ]] && HideErrors="2>/dev/null"

google-chrome --headless --disable-gpu --dump-dom \
    "$RobWebAddress" > "$DownloadName" "$HideErrors"

ฉันได้รับข้อความแสดงข้อผิดพลาด:

[0826/043058.634775:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.672587:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.711640:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
(... SNIP ...)

เหตุใดอาร์กิวเมนต์ที่ทำงานแบบตายตัวทำงานได้ แต่ไม่ใช่อาร์กิวเมนต์เป็นตัวแปร


แก้ไข 2:

ขณะนี้ฉันพบความสำเร็จด้วยคำแนะนำสำรองของคำตอบที่สอง:

# Redirect errors when cron is used to /dev/null to reduce emails
ErrorPipe=/dev/stderr
[[ $fCron == true ]] && ErrorPipe=/dev/null

google-chrome --headless --disable-gpu --dump-dom \
                "$RobWebAddress" > "$DownloadName" 2>"$ErrorPipe"

แก้ไข 1:

จากคำตอบแรกฉันควรชี้ให้เห็นว่าส่วนหัวของโปรแกรมประกอบด้วย:

[[ $fCron != true ]] &&
    exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)

คุณสามารถลอง[[ $fCron == true ]] && exec 2>/dev/nullแทนได้
steeldriver

.. พูดอย่างคร่าว ๆ ก็เพราะเชลล์ตั้งค่าการเปลี่ยนเส้นทางก่อนที่จะขยายตัวแปรฉันคิดว่า ดูตัวอย่างทุบตี: ใช้ตัวแปรเก็บ stderr | stdout redirection
steeldriver

คำตอบ:


19

เหตุผลที่คุณไม่สามารถทำให้เกิดการเปลี่ยนเส้นทางที่จะเกิดขึ้นโดยการขยาย"$HideErrors"คือสัญลักษณ์เช่น>ไม่ได้รับการปฏิบัติเป็นพิเศษหลังจากที่ถูกผลิตโดยการขยายตัวพารามิเตอร์ นี่เป็นสิ่งที่ดีมากเพราะสัญลักษณ์ดังกล่าวปรากฏในข้อความที่คุณอาจต้องการขยายและใช้อย่างแท้จริง

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


สำหรับสิ่งที่ต้องทำเกี่ยวกับมันมีหลายวิธีในการเปลี่ยนเส้นทางตามเงื่อนไข สำหรับคำสั่งที่ง่ายมากมันอาจจะสมเหตุสมผลในการเขียนคำสั่งทั้งหมดสองครั้งครั้งในแต่ละสาขาของcaseหรือif- elseสร้าง อย่างไรก็ตามในไม่ช้านี้จะกลายเป็นภาระและคำสั่งที่คุณแสดงนั้นเป็นกรณีที่ไม่เหมาะ

ในแนวทางที่ให้คุณหลีกเลี่ยงการทำซ้ำตัวเองมีสองฉันแนะนำโดยเฉพาะอย่างยิ่งเพราะพวกเขาค่อนข้างสะอาดและง่ายต่อการได้รับขวา คุณต้องการใช้เพียงหนึ่งอย่างต่อไปนี้ไม่ใช่ทั้งสองอย่างพร้อมกันสำหรับคำสั่งและการเปลี่ยนเส้นทางเดียวกัน

เก็บคำสั่งแทนการเปลี่ยนเส้นทาง แทนที่จะพยายามในการจัดเก็บการเปลี่ยนเส้นทางในตัวแปรและการประยุกต์ใช้การขยายตัวพารามิเตอร์เก็บคำสั่งในฟังก์ชั่นเปลือก จากนั้นเขียน a caseหรือif- elseซึ่งฟังก์ชันถูกเรียกพร้อมกับการเปลี่ยนเส้นทางในสาขาหนึ่งและไม่มีอยู่บนอีกสาขา

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

ด้วยรหัสของคุณ:

launch() {
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
}

case $fCron in
true)  launch 2>/dev/null;;
*)     launch;; # Get silly error messages when running from terminal
esac

คุณสามารถใช้การเว้นวรรคใดก็ได้ตามที่คุณต้องการหรือif- elseหากคุณต้องการ โปรดทราบว่าlaunchจะใช้ผู้เรียกRobWebAddressและDownloadNameตัวแปรโดยอัตโนมัติแม้ว่าจะเป็นตัวแปรในเครื่องเนื่องจาก Bash มีการกำหนดขอบเขตแบบไดนามิกซึ่งแตกต่างจากภาษาการเขียนโปรแกรมส่วนใหญ่ที่มีการกำหนดขอบเขตโดยทั่วไป

รันคำสั่งในเชลล์ย่อยและใช้การเปลี่ยนทิศทางกับexecเงื่อนไข นี่คือสิ่งที่steeldriver แสดงความคิดเห็นเกี่ยวกับแต่ภายใน( )เพื่อให้มีผลบังคับใช้ในท้องถิ่น เมื่อbuiltinจะดำเนินการกับการขัดแย้งไม่มีก็ไม่ได้แทนเปลือกปัจจุบันกับกระบวนการใหม่ แต่แทนที่จะใช้ใด ๆ ของการเปลี่ยนเส้นทางไปยังเปลือกปัจจุบันexec

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

ด้วยรหัสของคุณ:

(
    # Suppress silly error messages unless running from terminal
    case $fCron in true) exec 2>/dev/null;; esac

    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
)

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

วิธีการทั้งสองวิธีทำงานโดยไม่คำนึงถึงข้อผิดพลาดมาตรฐานของไฟล์หรืออุปกรณ์ที่เริ่มต้นรวมถึงในกรณีของการเปลี่ยนเส้นทางที่ใช้กับฟังก์ชันเชลล์ที่เรียกใช้รหัสที่มีพฤติกรรมตามเงื่อนไขเช่นเดียวกับกรณี (กล่าวถึงในการแก้ไขของคุณ) สคริปต์ทั้งหมดได้ถูกเปลี่ยนเส้นทางโดยก่อนหน้านี้หรือ เส้นทางที่ผลิตโดยการทดแทนกระบวนการนั้นไม่มีปัญหาexec 2>&fdexec 2> path


FYI SteelDriver พูดถึงบางสิ่งบางอย่างเกี่ยวกับexecไม่รู้ว่าเขากำลังวางแผนคำตอบหรือไม่ ...
WinEunuuchs2Unix

@ WinEunuuchs2Unix ฉันหวังว่าคำตอบดังกล่าวจะยังคงโพสต์ execแม้ว่าผมส่วนใหญ่แนะนำให้ใช้ฟังก์ชั่นที่ผมยังรวมถึงวิธีการที่เกี่ยวข้องกับการเปลี่ยนเส้นทางบน แต่อย่างที่ฉันพูดถึง parenthetically ฉันไม่ได้ครอบคลุมแอพพลิเคชั่นที่ซับซ้อนมากขึ้นซึ่งตัวอธิบายไฟล์เก่าถูกเก็บและกู้คืนโดยไม่ต้องมี subshell ฉันยังไม่ครอบคลุมแอพพลิเคชั่นที่ซับซ้อนน้อยกว่าเช่นเพียงแค่ทำการเปลี่ยนเส้นทางหากนี่เป็นจุดสิ้นสุดของสคริปต์ คำตอบอื่นถ้าโพสต์สามารถครอบคลุมทั้งสองและอาจมากกว่า
Eliah Kagan

ฉันได้อัปเดตคำถามของฉันกับสิ่งที่มีอยู่แล้วexecซึ่งไม่น่าส่งผลกระทบต่อคำตอบของคุณ แต่อย่างใด
WinEunuuchs2Unix

@ WinEunuuchs2Unix ใช่นั่นน่าจะไม่มีปัญหา ฉันได้เพิ่มย่อหน้าเกี่ยวกับท้ายคำตอบ
Eliah Kagan

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

4

เหตุใดอาร์กิวเมนต์ที่ทำงานแบบตายตัวทำงานได้ แต่ไม่ใช่อาร์กิวเมนต์เป็นตัวแปร

เนื่องจากรายการไวยากรณ์ไม่ได้ถูกตีความจากค่าตัวแปรที่ขยาย นั่นคือการขยายตัวของตัวแปรไม่เหมือนกับการแทนที่การอ้างอิงตัวแปรด้วยข้อความของตัวแปรในบรรทัดคำสั่ง (สิ่งที่ชอบ;, |, &&และคำพูด ฯลฯ นอกจากนี้ยังไม่ได้เป็นพิเศษในค่าของตัวแปร.)

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

นามแฝงเป็นเพียงการแทนที่ข้อความดังนั้นพวกเขาจึงสามารถเก็บรายการทางไวยากรณ์เช่นตัวดำเนินการและคำหลัก ในสคริปต์คุณจะต้องทำshopt expand_aliasesตามค่าเริ่มต้นพวกเขาจะปิดใช้งานในเชลล์ที่ไม่มีการโต้ตอบ ดังนั้นภาพนี้2(เฉพาะ):

#!/bin/bash
shopt -s expand_aliases

alias redir='> /dev/null'
redir echo 1
alias redir=''
redir echo 2

(และคุณสามารถalias jos=if niin=then soj=fiเขียนคำแถลง if ทั้งหมดของคุณเป็นภาษาฟินแลนด์ได้ฉันแน่ใจว่าทุกคนที่อ่านสคริปต์จะรักคุณ)

หรือเขียนการเปลี่ยนเส้นทางเสมอ แต่ควบคุมเฉพาะเป้าหมายด้วยตัวแปร คุณจะต้องมีเป้าหมายแบบไม่ต้องป้อนข้อมูลสำหรับกรณีที่คุณไม่ต้องการเปลี่ยนตำแหน่งที่ส่งออกแต่/dev/stderrควรทำงานในกรณีนั้น ที่จริงแล้วการเพิ่ม2> /dev/stderrไม่ได้เป็นตัวเลือกเพราะวิธีที่ Linux ปฏิบัติต่อ fd ที่เปิดจากที่/proc/<pid>/fdเป็นอิสระจากต้นฉบับ สิ่งนี้มีผลต่อการวางตำแหน่งของตำแหน่งการเขียนและจะทำให้การส่งออกยุ่งเหยิงหากมันไปยังไฟล์ปกติ

มันควรจะทำงานในโหมดผนวกแม้ว่า (หรือถ้า stderr ไปที่ไพพ์หรือเทอร์มินัล):

#!/bin/sh
exec 2>/tmp/error.log
dst=/dev/null
ls -l /nosuchfile-1 2>> "$dst"     # this doesn't print
dst=/dev/stderr
ls -l /nosuchfile-2 2>> "$dst"
ls -l /nosuchfile-3 2>> "$dst"

ดังนั้นการทำซ้ำ: 2> /dev/stderrสามารถทำลาย


ฮ่าฮ่าฮ่าฉันจะใช้งานภาษาฟินแลนด์เท่านั้นที่ทำงานจากนี้ :>
ของหวาน

ฉันชอบข้อเสนอแนะอื่น ความคิดของexpand_aliasesมันน่ากลัวเพราะ~/.bashrcผมคิดว่าโปรแกรมของคุณสามารถถูกจับเป็นตัวประกันได้
WinEunuuchs2Unix

1
@ WinEunuuchs2Unix ใช่expand_aliasesมันค่อนข้างน่ากลัว แต่~/.bashrcไม่น่าจะมีปัญหาเพราะมันเป็นแค่การอ่านโดยเชลล์แบบอินเตอร์แอคทีฟ.profileและเพื่อน ๆ ที่อาจเรียกมันว่ามันเป็นแบบอ่านอย่างเดียวโดยล็อกอินเชลล์ เชลล์แบบไม่มีการโต้ตอบที่ไม่ใช่การเข้าสู่ระบบเช่นสคริปต์ไม่ควรเรียกใช้เชลล์เหล่านี้ (แต่แล้วก็มี$BASH_ENVและเห็นได้ชัดว่า.bashrcอ่านถ้า stdin เชื่อมต่อกับซ็อกเก็ตเครือข่ายมันจะได้รับที่ซับซ้อน ... )
14252

ฉันเข้าใจคำแนะนำอื่นของคุณอย่างสมบูรณ์แบบและฉันจะลองคืนนี้ :)
WinEunuuchs2Unix

สุจริตฉันไม่แน่ใจว่าวิธีที่ฉันจะใช้นี้ถ้าฉันต้อง ฉันอาจเก็บคำสั่งในฟังก์ชั่นหรืออาร์เรย์แล้วสาขาเพื่อตัดสินใจว่าจะเปลี่ยนเส้นทางที่นั่น (ใช้ฟังก์ชั่นที่แสดงในคำตอบอื่น ๆ ) หรือ2>> "$dst"เคล็ดลับนั้น แต่ฉันเพิ่งรู้ว่ามันใช้ไม่ได้ในกรณีทั่วไปดังนั้นควรระวังให้ดีกว่า
ilkkachu

1

ชื่อคำถาม: "จะผ่าน 2> / dev / null เป็นตัวแปรได้อย่างไร" สามารถทำได้จริงโดยใช้eval

joshua@nova:/tmp$ X=">/dev/null"
joshua@nova:/tmp$ echo $X
>/dev/null
joshua@nova:/tmp$ eval echo $X
joshua@nova:/tmp$ eval echo hi
hi
joshua@nova:/tmp$ eval echo hi $X
joshua@nova:/tmp$ echo hi $X
hi >/dev/null
joshua@nova:/tmp$ 

ดังนั้นเราสามารถเขียนใหม่เป็น

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
local RobWebAddress2
local DownloadName2
[[ $fCron == true ]] && HideErrors="2>/dev/null"
RobWebAddress2='"$RobWebAddress"'
DownloadName2='>"$DownloadName"'

eval google-chrome --headless --disable-gpu --dump-dom \
    $RobWebAddress2 $DownloadName2 "$HideErrors"

ในกรณีที่การเข้าถึงตัวแปรทางอ้อมป้องกันไม่ให้เกิดการขยายตัวเกิดขึ้นเร็วเกินไปในส่วนที่เหลือของบรรทัดคำสั่ง

เครื่องหมายคำพูดคู่ในตัวแปรใช้งานได้ดี

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$ 

@EliahKagan: ชื่อคำถาม: "จะผ่าน 2> / dev / null เป็นตัวแปรได้อย่างไร"
โจชัว

ตกลงมันไม่ทำงาน ฉันได้แก้ไขแล้ว
โจชัว

ตอนนี้ไฟล์จะถูกตั้งชื่อเสมอDownloadName- และตัวอักษรRobWebAddressจะถูกใช้สำหรับ URL เสมอ คุณกำลังใช้อ้าง$" " ฉันคิดว่านี่อาจไม่ได้ตั้งใจและคุณอาจต้องการให้$อยู่ข้างใน" "แต่คุณก็ทำแบบนั้นในทั้งสองแห่งดังนั้นฉันไม่แน่ใจ ฉันคิดว่า> "$DownloadName"ควรแก้ไข แต่ฉันเข้าใจว่าคุณอาจไม่ชอบสิ่งนั้นเนื่องจากการผสมอาร์กิวเมนต์โดยไม่ได้ตั้งใจกับข้อโต้แย้งด้วยกันevalเป็นเหตุผลหนึ่งที่ทำให้มีความเสี่ยงและหมดกำลังใจอย่างมากในการใช้evalพฤติกรรมที่เชื่อมโยงกัน
Eliah Kagan

@EliahKagan: โอ้ เชลล์ที่ฉันต้องการใช้ในการเขียนสคริปต์ไม่ได้มีข้อความ $ ""
โจชัว

1
ถ้าคุณแก้ไขมันควรจะทำงาน และฉันผิดเสมอที่คิดว่ามันวางคำพูดในข้อความที่กำหนดเอง มันสร้างข้อโต้แย้งที่แท้จริงที่evalเชื่อมต่อกันก่อนที่จะประเมิน แต่ฉันคิดว่าอีกวิธีหนึ่งที่จะนำมาใช้ก็คือมันเป็นวิธีที่สับสนที่จะเขียนeval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors"ซึ่งคล้ายกับการปรากฏตัวของรหัสของ OP และโดยทั่วไปใช้evalสำหรับงานที่ไม่จำเป็นต้องมันไม่ดี (ไม่มีข้อแก้ตัวใด ๆ - หรือแม้กระทั่งอธิบาย - ความผิดและความเกลียดชังของคำตอบเก่าของฉัน)
Eliah Kagan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.