Jak przekazać 2> / dev / null jako zmienną?

13

Mam ten kod, który działa:

# 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

Jeśli spróbuję skrócić to w ten sposób:

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

Otrzymuję komunikaty o błędach:

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

Dlaczego argument zakodowany na stałe działa, a nie argument jako zmienna?


Edycja 2:

Obecnie odnalazłem sukces z alternatywną sugestią drugiej odpowiedzi:

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

Edycja 1:

Na podstawie pierwszej odpowiedzi powinienem zaznaczyć, że nagłówek programu już zawiera:

[[ $fCron != true ]] &&
    exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)
WinEunuuchs2Unix
źródło
Możesz spróbować [[ $fCron == true ]] && exec 2>/dev/nullzamiast tego
steeldriver
.. z grubsza mówiąc, to dlatego, że powłoka ustawia przekierowania przed rozszerzaniem zmiennych, tak myślę. Zobacz na przykład bash: użyj zmiennej do przechowywania przekierowania stderr | stdout
steeldriver

Odpowiedzi:

19

Powodem, dla którego nie można spowodować przekierowania przez rozwinięcie, "$HideErrors"jest to, że takie symbole >nie są traktowane specjalnie po wytworzeniu przez rozwinięcie parametru . To jest naprawdę bardzo dobre, ponieważ takie symbole pojawiają się w tekście, który możesz chcieć rozwinąć i używać dosłownie.

Dotyczy to tego, czy zacytowałeś czy nie $HideErrors. Wynik interpretacji parametrów podlega dzieleniu i globowaniu słów, gdy interpretacja nie jest cytowana , ale to wszystko.


Jeśli chodzi o to, co z tym zrobić, istnieje wiele sposobów osiągnięcia warunkowego przekierowania. Przez bardzo prostego polecenia, może być uzasadnione zapisem cała komenda dwa razy, raz w każdej gałęzi caselub if- elsekonstruktem. Szybko staje się to jednak uciążliwe, a polecenie, które pokazałeś, z pewnością nie byłoby idealne.

Spośród metod, które pozwalają uniknąć powtarzania się , szczególnie polecam dwa, ponieważ są one dość czyste i łatwo je naprawić. Chciałbyś użyć tylko jednego z nich, a nie obu naraz dla tego samego polecenia i przekierowania.

Zapisz polecenie zamiast przekierowania. Zamiast próbować zapisać przekierowanie w zmiennej i zastosować rozszerzenie parametrów, zapisz polecenie w funkcji powłoki . Następnie napisz caselub if- else, w którym funkcja jest wywoływana z przekierowaniem na jednym oddziale, a bez niego na drugim.

Jeśli konceptualizujesz swoje polecenie jako kod, który chcesz napisać raz, ale działa w wielu okolicznościach, funkcja jest naturalnym rozwiązaniem. Tak zwykle robię. Ma tę zaletę, że nie wymaga ani podpowłoki, ani ręcznego przechowywania i resetowania stanu.

Z twoim kodem:

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

Możesz zastosować dowolne odstępy lub if- elsezamiast tego, jeśli wolisz. Zauważ, że launchautomatycznie używa wywołującego RobWebAddressi DownloadNamezmiennych, nawet jeśli są to zmienne lokalne, ponieważ Bash ma zakres dynamiczny , w przeciwieństwie do większości języków programowania, które mają zasięg leksykalny.

Uruchom polecenie w podpowłoce i warunkowo zastosuj przekierowanie do exec. O tym skomentował steeldriver , ale w środku, ( )aby efekt był lokalny . Kiedy wbudowane jest uruchamiany bez argumentów, nie zastąpi obecny powłoki z nowego procesu, lecz stosuje się którykolwiek z jego przekierowań do bieżącej powłoki.exec

(Możliwe jest również śledzenie, jaki był standardowy błąd i przywrócenie go, bez użycia podpowłoki, a tym samym bez poświęcania możliwości modyfikowania bieżącego środowiska powłoki. Zostawię to jednak szczegółowi innym odpowiedziom.)

Z twoim kodem:

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

Po zamknięciu )standardowy błąd jest przywracany do poprzedniego stanu, ponieważ jest przekierowywany tylko w podpowłoce, a nie w powłoce nadrzędnej. To również działa dobrze z istniejącymi zmiennymi powłoki, ponieważ podpowłoki otrzymują ich kopię. Chociaż wolę używać funkcji powłoki, przyznaję, że ta metoda może wymagać mniej kodu.

Obie metody działają niezależnie od tego, jak zaczyna się standardowy błąd pliku lub urządzenia, w tym w przypadku przekierowań zastosowanych do funkcji powłoki, które wywołują kod zawierający zachowanie warunkowe, a także w przypadku (wymienionym w Twojej edycji), w którym błąd standardowy dla cały skrypt został już przekierowany przez poprzednią lub . To, że ścieżka została utworzona przez podstawienie procesu, nie stanowi problemu.exec 2>&fdexec 2> path

Eliah Kagan
źródło
FYI SteelDriver wspomniał o czymś execnie wiem, czy planuje odpowiedź na to ...
WinEunuuchs2Unix
@ WinEunuuchs2Unix Mam nadzieję, że taka odpowiedź jest nadal opublikowana. Chociaż głównie polecam korzystanie z funkcji, dołączyłem również metodę polegającą na przekierowaniu na exec. Ale, jak wspomniałem w nawiasie, nie obejmowałem bardziej wyrafinowanych aplikacji, w których stary deskryptor pliku jest przechowywany i odtwarzany bez podpowłoki. Nie obejmowałem również mniej skomplikowanych aplikacji, takich jak utrzymywanie przekierowania, jeśli to koniec skryptu. Inna odpowiedź, jeśli zostanie opublikowana, może obejmować jedno i drugie.
Eliah Kagan
Zaktualizowałem moje pytanie istniejącym, execco, jak sądzę, nie powinno wpłynąć na twoją odpowiedź.
WinEunuuchs2Unix
@ WinEunuuchs2Unix Tak, to nie powinno być problemu. Na końcu odpowiedzi dodałem akapit na ten temat.
Eliah Kagan
Ciekawe odkrycie czytające twoją odpowiedź, RobWebAddressto zdecydowanie kontekst globalny. DownloadNamezostał zdefiniowany lokalnie, ale powinien mieć kontekst globalny. Z jakiegoś powodu funkcje potomne dziedziczą lokalne definicje rodziców (dowcip DownloadNamebył widoczny dla DownloadAsHTML ()funkcji wywoływanej przez UpdateOne ()funkcję, która zdefiniowała ją jako lokalną. To był trudny dzień :(
WinEunuuchs2Unix
4

Dlaczego argument zakodowany na stałe działa, a nie argument jako zmienna?

Ponieważ elementy składniowe nie są interpretowane na podstawie wartości zmiennych rozszerzonych. Oznacza to, że interpretacja zmiennej nie jest tym samym, co zamiana odwołania zmiennej na tekst zmiennej w wierszu poleceń. (Rzeczy jak ;, |, &&są również i cytaty itp nie specjalne w wartościach zmiennych).

Możesz użyć aliasów lub użyć zmiennej do przechowywania tylko celu przekierowania.

Aliasy tylko zamiennikiem tekstu, więc mogą zawierać elementy składniowe, takie jak operatory i słowa kluczowe. W skrypcie należy to zrobić shopt expand_aliases, ponieważ domyślnie są one wyłączone w nieinteraktywnych powłokach. Więc to drukuje 2(tylko):

#!/bin/bash
shopt -s expand_aliases

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

(I możesz także, alias jos=if niin=then soj=fia następnie napisać wszystkie swoje instrukcje if w języku fińskim. Jestem pewien, że każdy, kto przeczyta skrypt, pokochałby cię)

Alternatywnie, zawsze napisz przekierowanie, ale kontroluj tylko cel za pomocą zmiennej. Będziesz potrzebował celu braku op dla przypadku, w którym nie chcesz zmieniać, gdzie idzie wyjście, ale /dev/stderrpowinien działać w tym przypadku. W rzeczywistości dodawanie 2> /dev/stderrnie jest opcją, ponieważ Linux traktuje otwarte fd /proc/<pid>/fdjako niezależne od oryginału. Wpływa to na pozycjonowanie pozycji zapisu i psuje dane wyjściowe, jeśli przejdzie do zwykłego pliku.

Powinien jednak działać w trybie dołączania (lub jeśli stderr idzie do potoku lub terminala):

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

Powtarzam: 2> /dev/stderrmoże się zepsuć.

ilkkachu
źródło
Hahaha, od tej pory będę używać fińskiego tylko w pracy. :>
deser
Lubię alternatywne sugestie. Myśl ta expand_aliasesjest przerażająca, ponieważ ~/.bashrcmyślę, że twój program może być zakładnikiem .
WinEunuuchs2Unix
1
@ WinEunuuchs2Unix, tak, expand_aliasesto trochę przerażające. Ale ~/.bashrcnie powinno być problemu, ponieważ jest tylko do odczytu przez powłok interaktywnych i .profilei przyjaciele, że może to nazwać są czytane tylko przez powłok zgłoszeniowych. Nieinteraktywne powłoki niezalogowane, takie jak skrypty, nie powinny uruchamiać żadnej z nich. (Ale potem jest $BASH_ENVi najwyraźniej .bashrcjest czytany, jeśli stdin jest podłączony do gniazda sieciowego. Jak skomplikowane może się dostać ...)
ilkkachu
Cóż, doskonale rozumiem twoją alternatywną sugestię i spróbuję ją dziś wieczorem :)
WinEunuuchs2Unix
Szczerze mówiąc, nie jestem pewien, w jaki sposób zastosowałbym to, gdybym musiał. Prawdopodobnie zapisałbym polecenie w funkcji lub tablicy, a następnie rozgałęził się, aby zdecydować, czy umieścić tam przekierowanie (użycie funkcji pokazano w innej odpowiedzi). Lub ta 2>> "$dst"sztuczka, ale właśnie zdałem sobie sprawę, że to nie działa w ogólnym przypadku, więc lepiej bądź ostrożny.
ilkkachu
1

Tytuł pytania: „Jak przekazać 2> / dev / null jako zmienną?” Można to faktycznie zrobić za pomocą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$ 

Więc możemy przepisać jako

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

Gdzie pośredni dostęp do zmiennych zapobiega zbyt szybkiemu rozszerzaniu w pozostałej części wiersza poleceń.

Podwójne cudzysłowy w zmiennych działają dobrze.

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$ 
Jozuego
źródło
@EliahKagan: Tytuł pytania: „Jak przekazać 2> / dev / null jako zmienną?”
Joshua
Ok, to nie działało. Naprawiłem to.
Joshua
Teraz plik ma zawsze nazwę - DownloadNamei dosłownie tekst RobWebAddressjest zawsze używany jako adres URL. Używasz $" "cytowania . Myślę, że może to być niezamierzone i możesz chcieć dostać się do $środka " ", ale zrobiłeś to w obu miejscach, więc nie jestem pewien. Myślę, że > "$DownloadName"powinienem to naprawić. Rozumiem jednak, że może ci się to nie podobać, ponieważ przypadkowe pomieszanie argumentów z nie-argumentami evaljest jednym z powodów, dla których tak niebezpieczne i szeroko zniechęcone jest stosowanie evalzachowania konkatenacji.
Eliah Kagan
@EliahKagan: Oh. Moja preferowana powłoka do tworzenia skryptów nie ma cytowania $ "".
Joshua
1
Jeśli to naprawisz, powinno działać. I zawsze myliłem się, sądząc, że wklejało cytaty na dowolny tekst! Buduje dosłowne argumenty, które evalkonkatenują przed oceną. Ale myślę, że innym sposobem jest to, że jest to zaciemniony sposób pisania, eval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors"który przypomina wygląd kodu OP. Ogólnie rzecz biorąc, korzystanie evalz zadań, które go nie potrzebują, jest złe . (Żadne z tych usprawiedliwień - ani nawet nie wyjaśnia
złej