Bagaimana cara melewati 2> / dev / null sebagai variabel?

13

Saya memiliki kode ini yang berfungsi:

# 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

Jika saya mencoba mempersingkatnya seperti ini:

# 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"

Saya mendapat pesan kesalahan:

[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 ...)

Mengapa argumen hard-coded berfungsi tetapi bukan argumen sebagai variabel?


Edit 2:

Saat ini saya menemukan kesuksesan dengan saran alternatif jawaban kedua:

# 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"

Edit 1:

Berdasarkan jawaban pertama, saya harus menunjukkan header program yang sudah berisi:

[[ $fCron != true ]] &&
    exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)
WinEunuuchs2Unix
sumber
Anda dapat mencoba [[ $fCron == true ]] && exec 2>/dev/nullsebagai gantinya
steeldriver
.. secara kasar, itu karena shell mengatur pengalihan sebelum memperluas variabel, saya pikir. Lihat misalnya bash: Gunakan variabel untuk menyimpan stderr | stdout redirection
steeldriver

Jawaban:

19

Alasan Anda tidak dapat menyebabkan pengalihan terjadi dengan memperluas "$HideErrors"adalah bahwa simbol seperti >tidak diperlakukan secara khusus setelah diproduksi oleh ekspansi parameter . Ini sebenarnya sangat bagus, karena simbol seperti itu muncul dalam teks yang mungkin ingin Anda kembangkan dan gunakan secara harfiah.

Ini berlaku apakah Anda mengutip atau tidak $HideErrors. Hasil ekspansi parameter tunduk pada pemisahan kata dan globbing ketika ekspansi tersebut tidak dikutip, tetapi hanya itu saja.


Adapun apa yang harus dilakukan tentang hal itu, ada banyak cara untuk mencapai pengalihan bersyarat. Untuk perintah yang sangat sederhana, mungkin masuk akal menulis seluruh perintah dua kali, sekali di setiap cabang caseatau if- elsekonstruksi. Namun, ini segera menjadi beban, dan perintah yang Anda tunjukkan tentu saja merupakan kasus yang tidak ideal.

Dari pendekatan yang memungkinkan Anda menghindari pengulangan sendiri , ada dua yang saya sarankan, karena mereka cukup bersih dan mudah diperbaiki. Anda ingin menggunakan hanya satu di antaranya, tidak sekaligus untuk perintah dan pengalihan yang sama.

Simpan perintah alih-alih pengalihan. Alih-alih mencoba menyimpan pengalihan dalam variabel dan menerapkan perluasan parameter, simpan perintah dalam fungsi shell . Kemudian tulis a caseatau if- else, di mana fungsi dipanggil dengan pengalihan di satu cabang dan tanpa itu di yang lain.

Jika Anda mengonseptualisasikan perintah sebagai kode yang ingin Anda tulis sekali tetapi dijalankan dalam berbagai keadaan, maka fungsi adalah solusi alami. Inilah yang biasanya saya lakukan. Ini memiliki manfaat tidak memerlukan subkulit atau penyimpanan manual dan pengaturan ulang negara.

Dengan kode Anda:

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

Anda dapat menerapkan jarak apa pun yang Anda suka, atau if- elsesebagai gantinya jika Anda mau. Perhatikan bahwa launchsecara otomatis menggunakan pemanggil RobWebAddressdan DownloadNamevariabel, bahkan jika mereka adalah variabel lokal, karena Bash secara dinamis dicakup , tidak seperti kebanyakan bahasa pemrograman yang dibatasi secara leksikal.

Jalankan perintah dalam subkulit dan gunakan pengalihan secara kondisional exec. Inilah yang steeldriver berkomentar tentang , tetapi di dalam ( )untuk menjaga efek lokal . Ketika para execbuiltin dijalankan tanpa argumen, itu tidak menggantikan shell saat ini dengan proses baru, tapi bukannya berlaku setiap dari pengalihan kepada shell saat ini.

(Mungkin juga untuk melacak kesalahan standar dan mengembalikannya, tanpa menggunakan subshell dan dengan demikian tanpa mengorbankan kemampuan untuk memodifikasi lingkungan shell saat ini. Namun, saya akan menyerahkan detailnya ke jawaban lain.)

Dengan kode Anda:

(
    # 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"
)

Setelah penutupan ), kesalahan standar berlaku dikembalikan ke apa pun sebelumnya, karena itu hanya benar-benar diarahkan di subkulit, dan bukan di shell induk. Ini juga berfungsi baik dengan variabel shell yang ada, karena subshell mendapatkan salinannya. Meskipun saya lebih suka menggunakan fungsi shell, saya akui metode ini mungkin memerlukan lebih sedikit kode.

Kedua metode berfungsi terlepas dari apa standar kesalahan file atau perangkat dimulai, termasuk dalam kasus pengalihan diterapkan ke fungsi shell yang memanggil kode yang berisi perilaku kondisional, serta kasus (disebutkan dalam edit Anda) di mana kesalahan standar untuk seluruh skrip telah dialihkan oleh yang sebelumnya atau . Bahwa jalur diproduksi oleh proses substitusi tidak ada masalah.exec 2>&fdexec 2> path

Eliah Kagan
sumber
FYI SteelDriver menyebutkan sesuatu tentang exectidak tahu apakah dia merencanakan jawaban untuk itu ...
WinEunuuchs2Unix
@ WinEunuuchs2Unix Saya harap jawaban seperti itu masih diposting. Meskipun saya terutama merekomendasikan menggunakan fungsi, saya juga menyertakan metode yang melibatkan pengalihan exec. Tapi, seperti yang saya sebutkan di atas, saya tidak membahas aplikasi yang lebih canggih di mana deskriptor file lama disimpan dan dipulihkan tanpa subkulit. Saya juga tidak membahas aplikasi yang kurang canggih, seperti hanya menjaga pengalihan jika ini adalah akhir dari skrip. Jawaban lain, jika diposting, dapat mencakup keduanya, dan mungkin lebih.
Eliah Kagan
Saya telah memperbarui pertanyaan saya dengan yang sudah ada execyang seharusnya tidak mempengaruhi jawaban Anda sama sekali.
WinEunuuchs2Unix
@ WinEunuuchs2Unix Ya, itu seharusnya tidak menjadi masalah. Saya telah menambahkan satu paragraf tentang itu di akhir jawaban.
Eliah Kagan
Wahyu yang menarik membaca jawaban Anda, RobWebAddressjelas merupakan konteks global. DownloadNamedidefinisikan lokal tetapi harus konteks global. Untuk beberapa alasan fungsi anak mewarisi definisi lokal dari orang tua (untuk kecerdasan DownloadNameitu terlihat dengan DownloadAsHTML ()fungsi yang disebut oleh UpdateOne ()fungsi yang mendefinisikannya sebagai lokal. Sudah hari yang sulit :(
WinEunuuchs2Unix
4

Mengapa argumen hard-coded berfungsi tetapi bukan argumen sebagai variabel?

Karena item sintaks tidak ditafsirkan dari nilai variabel yang diperluas. Artinya, ekspansi variabel tidak sama dengan mengganti referensi variabel dengan teks variabel di baris perintah. (Hal-hal seperti ;, |, &&dan kutipan dll juga tidak khusus dalam nilai-nilai variabel.)

Yang bisa Anda lakukan adalah menggunakan alias, atau menggunakan variabel untuk menahan hanya target pengalihan.

Alias yang hanya pengganti teks, sehingga mereka dapat terus item sintaksis, seperti operator dan kata kunci. Dalam skrip, Anda harus melakukannya shopt expand_aliases, karena secara default skrip tersebut dinonaktifkan di shell yang tidak interaktif. Jadi, ini mencetak 2(hanya):

#!/bin/bash
shopt -s expand_aliases

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

(Dan Anda juga bisa alias jos=if niin=then soj=fidan kemudian menulis semua pernyataan if Anda dalam bahasa Finlandia. Saya yakin siapa pun yang membaca naskah akan menyukai Anda.)

Atau, tulis pengalihan selalu, tetapi kontrol hanya target dengan variabel. Anda memerlukan target no-op untuk kasus di mana Anda tidak ingin mengubah ke mana output berjalan, tetapi /dev/stderrharus bekerja dalam kasus itu. Sebenarnya, menambahkan 2> /dev/stderrbukan merupakan larangan karena cara Linux memperlakukan fd dibuka dari /proc/<pid>/fdindependen dari aslinya. Ini memengaruhi posisi posisi tulis dan akan mengacaukan output jika masuk ke file biasa.

Ini harus bekerja dalam mode tambahan, meskipun (atau jika stderr pergi ke pipa atau ke terminal):

#!/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"

Jadi ulangi: 2> /dev/stderrbisa pecah.

ilkkachu
sumber
Hahaha, saya hanya akan menggunakan ifs Finlandia di kantor mulai sekarang. :>
hidangan penutup
Saya suka saran alternatif. Pikiran expand_aliasesitu menakutkan karena program Anda dapat disandera oleh ~/.bashrcsaya pikir.
WinEunuuchs2Unix
1
@ WinEunuuchs2Unix, ya, expand_aliasesagak menakutkan. Tetapi ~/.bashrcseharusnya tidak menjadi masalah karena hanya dibaca oleh shell interaktif, dan .profiledan teman-teman yang mungkin menyebutnya hanya dibaca oleh shell login. Kerang non-login non-aktif seperti skrip seharusnya tidak menjalankan semua itu. (Tapi kemudian ada $BASH_ENV, dan ternyata .bashrcdibaca jika stdin terhubung ke soket jaringan. Betapa berbelit-belit itu bisa ...)
ilkkachu
Yah saya mengerti saran alternatif Anda dengan sempurna dan saya akan mencobanya malam ini :)
WinEunuuchs2Unix
Jujur, saya tidak yakin ke arah mana saya akan menerapkan ini jika saya harus. Saya mungkin akan menyimpan perintah dalam suatu fungsi atau array dan kemudian cabang untuk memutuskan apakah akan menempatkan pengalihan di sana (menggunakan fungsi ditunjukkan pada jawaban lain). Atau 2>> "$dst"trik itu, tapi saya baru sadar itu tidak berfungsi dalam kasus umum, jadi lebih baik hati-hati dengan itu.
ilkkachu
1

Judul pertanyaan: "Bagaimana cara lulus 2> / dev / null sebagai variabel?" Ini sebenarnya bisa dilakukan menggunakaneval

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$ 

Jadi kita dapat menulis ulang sebagai

# 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"

Di mana akses variabel tidak langsung mencegah ekspansi terjadi terlalu cepat pada sisa baris perintah.

Kutipan ganda dalam variabel berfungsi dengan baik.

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$ 
Joshua
sumber
@EliahKagan: Judul pertanyaan: "Bagaimana cara lulus 2> / dev / null sebagai variabel?"
Yosua
Ok itu tidak berfungsi. Saya sudah memperbaikinya.
Joshua
Sekarang file selalu bernama DownloadName- dan teks literal RobWebAddressselalu digunakan untuk URL. Anda menggunakan $" "kutipan . Saya pikir ini mungkin tidak disengaja dan Anda mungkin ingin $di dalam " ", tetapi Anda melakukannya dengan cara itu di kedua tempat, jadi saya tidak yakin. Saya pikir hanya > "$DownloadName"harus memperbaikinya. Tapi saya mengerti Anda mungkin tidak suka itu, karena tidak sengaja menggabungkan argumen dan non-argumen dengan evaladalah salah satu alasan itu sangat berbahaya dan sangat tidak disarankan untuk menggunakan evalperilaku gabungan.
Eliah Kagan
@EliahKagan: Oh. Shell pilihan saya untuk skrip tidak memiliki kutipan "" $.
Joshua
1
Jika Anda memperbaikinya, itu akan berhasil. Dan saya selalu salah untuk berpikir itu menyisipkan kutipan pada teks yang berubah-ubah! Itu membangun argumen literal yang evalmenyatukan sebelum mengevaluasi. Tapi saya pikir cara lain untuk menuliskannya adalah cara menulis eval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors"yang membingungkan yang menyerupai tampilan kode OP. Dan secara umum, menggunakan evaluntuk tugas-tugas yang tidak membutuhkannya itu buruk . (Tidak satu pun dari alasan ini - atau bahkan menjelaskan - kesalahan dan permusuhan balasan lama saya.)
Eliah Kagan