Pytanie Jak sprawdzić, czy program istnieje ze skryptu Bash?


W jaki sposób mogę sprawdzić, czy program istnieje, w sposób, który zwróci błąd i wyjdzie, lub będzie kontynuował działanie skryptu?

Wygląda na to, że powinno być łatwo, ale mnie to poturbowało.


1582
2018-02-26 21:52


pochodzenie




Odpowiedzi:


Odpowiedź

Zgodny z POSIX:

command -v <the_command>

Dla bash określone środowiska:

hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords

Wyjaśnienie

Uniknąć which. Nie tylko jest to proces zewnętrzny, który uruchamiasz w bardzo niewielkim stopniu (np. W stylu wbudowanym hash, type lub command są o wiele tańsze), możesz także polegać na wbudowanych funkcjach, aby faktycznie robić to, co chcesz, podczas gdy efekty zewnętrznych poleceń mogą z łatwością różnić się w zależności od systemu.

Dlaczego dbać?

  • Wiele systemów operacyjnych ma which że nawet nie ustawia statusu wyjścia, co oznacza if which foo nawet tam nie będzie pracować i będzie zawsze Zgłoś to foo istnieje, nawet jeśli tak nie jest (zauważ, że niektóre powłoki POSIX wydają się to robić hash także).
  • Wiele systemów operacyjnych which rób niestandardowe i złe rzeczy, takie jak zmiana wyjścia, a nawet dołączenie do menedżera pakietów.

Więc nie używaj which. Zamiast tego użyj jednego z następujących:

$ command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }
$ type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }
$ hash foo 2>/dev/null || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }

(Drobna notatka: niektórzy będą sugerować 2>&- Jest taki sam 2>/dev/null ale krócej - to nieprawda. 2>&- zamyka FD 2, co powoduje błąd w programie, gdy próbuje zapisać na stderr, co bardzo różni się od pomyślnego zapisania do niego i odrzucenia wyjścia (i niebezpiecznego!))

Jeśli twój hasz jest /bin/sh wtedy powinieneś zadbać o to, co mówi POSIX. type i hashkody wyjścia nie są bardzo dobrze zdefiniowane przez POSIX, i hash widać, że kończy się pomyślnie, gdy polecenie nie istnieje (nie widziałem tego z type jeszcze). commandStatus wyjścia jest dobrze zdefiniowany przez POSIX, więc prawdopodobnie jest najbezpieczniejszy w użyciu.

Jeśli używasz skryptu bash jednak zasady POSIX już nie mają znaczenia i jedno i drugie type i hash stać się całkowicie bezpieczny w użyciu. type teraz ma -P wyszukać tylko PATH i hash ma efekt uboczny, że lokalizacja polecenia zostanie zahartowana (w celu szybszego wyszukiwania przy następnym użyciu), co zwykle jest dobre, ponieważ prawdopodobnie sprawdzasz jego istnienie, aby faktycznie z niego korzystać.

Prostym przykładem jest tutaj uruchomiona funkcja gdate jeśli istnieje, w przeciwnym razie date:

gnudate() {
    if hash gdate 2>/dev/null; then
        gdate "$@"
    else
        date "$@"
    fi
}

2286
2018-03-24 12:45



@Geert: Część &> / dev / null ukrywa komunikat "type", gdy "foo" nie istnieje. Znak> i 2 na echo zapewnia wysłanie komunikatu o błędzie do standardowego błędu zamiast standardowego wyjścia; ponieważ to jest konwencja. Oba są wyświetlane na twoim terminalu, ale standardowy błąd jest zdecydowanie preferowanym wyjściem dla komunikatów o błędach i nieoczekiwanych ostrzeżeń. - lhunath
flaga -P nie działa w "sh", np stackoverflow.com/questions/2608688/... - momeara
Dla tych, którzy nie są zaznajomieni z "zaawansowanym" przekierowaniem wejścia / wyjścia w bashu: 1) 2>&-  ("zamknij deskryptor pliku wyjściowego 2", który jest stderr) ma taki sam wynik, jak 2> /dev/null; 2) >&2 jest skrótem dla 1>&2, które możesz rozpoznać jako "przekierowanie stdout na stderr". Zobacz Advanced Bash Scripting Guide strona przekierowania po więcej informacji. - mikewaters
To rozwiązanie nie działa z FreeBSD / sh. hash na FreeBSD zawsze zwraca kod 0 - jyavenard
@mikewaters 2>&- jest nie taki sam jak 2>/dev/null. Ten pierwszy zamyka deskryptor pliku, podczas gdy drugi po prostu przekierowuje go /dev/null. Możesz nie zobaczyć błędu, ponieważ program próbuje poinformować cię na stderr, że stderr jest zamknięty. - nyuszika7h


Poniżej przedstawiono przenośny sposób sprawdzania, czy polecenie istnieje $PATH  i jest wykonywalny:

[ -x "$(command -v foo)" ]

Przykład:

if ! [ -x "$(command -v git)" ]; then
  echo 'Error: git is not installed.' >&2
  exit 1
fi

Kontrola wykonywalna jest potrzebna, ponieważ bash zwraca plik niewykonywalny, jeśli nie znaleziono pliku wykonywalnego o tej nazwie $PATH.

Zauważ również, że jeśli plik niewykonywalny o takiej samej nazwie jak plik wykonywalny istnieje wcześniej $PATH, dash zwraca pierwszą, nawet jeśli ta ostatnia byłaby wykonana. Jest to błąd i stanowi naruszenie standardu POSIX. [Zgłoszenie błędu] [Standard]

Ponadto spowoduje to niepowodzenie, jeśli poszukiwane polecenie zostało zdefiniowane jako alias.


239
2017-11-05 14:33



Podoba mi się ta odpowiedź, ponieważ test jest łatwy w użyciu w skrypcie. Nie wymaga tworzenia funkcji. - Stephen Ostermiller
Będzie command -v wytworzyć ścieżkę nawet dla niewykonywalnego pliku? Czyli -x naprawdę jest konieczne? - einpoklum
@einpoklum -x sprawdza, czy plik jest wykonywalny, o to właśnie chodziło pytanie. - Ken Sharp
@KenSharp: Ale to wydaje się zbędne, ponieważ command sam będzie sprawdzał, czy jest wykonywany - prawda? - einpoklum
@einpoklum Tak, jest to konieczne. W rzeczywistości nawet to rozwiązanie może pęknąć w jednym przypadku krawędzi. Dziękuję za zwrócenie na to uwagi. dash, bash i zsh - wszystkie pomijają pliki inne niż pliki wykonywalne w $PATH podczas wykonywania polecenia. Jednak zachowanie command -v jest bardzo niespójne. W desce rozdzielczej zwraca pierwszy pasujący plik $PATH, niezależnie od tego, czy jest to plik wykonywalny czy nie. W bash zwraca pierwszy plik wykonywalny w $PATH, ale jeśli go nie ma, może zwrócić niewykonywalny plik. I w zsh, to nigdy nie zwróci niewykonywalnego pliku. - nyuszika7h


Zgadzam się z lhunath, aby zniechęcić do używania which, a jego rozwiązanie jest całkowicie poprawne dla użytkowników BASH. Jednakże, aby być bardziej przenośnym, command -v stosuje się zamiast tego:

$ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed.  Aborting." >&2; exit 1; }

Dowództwo command jest zgodny z POSIX, patrz tutaj dla specyfikacji: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html

Uwaga: type jest zgodny z POSIX, ale type -P nie jest.


190
2018-01-24 18:16



Jak powyżej - exit 1; zabija xterm, jeśli zostanie wywołany. - user unknown
Nie działałoby to na standardowym sh: you &> nie jest prawidłową instrukcją przekierowania. - jyavenard
@jyavenard: Pytanie jest oznaczone grzmotnąć, stąd bardziej zwięzły zapis przekierowania specyficzny dla basha &>/dev/null. Jednak zgadzam się z Tobą, co naprawdę jest ważne, to przenośność, Odpowiednio zredagowałem swoją odpowiedź, teraz używając standardowego przekierowania sh >/dev/null 2>&1. - GregV
aby jeszcze bardziej poprawić tę odpowiedź, zrobiłbym dwie rzeczy: 1: użyj "&>", aby ją uprościć, na przykład odpowiedź Josha. 2: podziel {{} na dodatkową linię, umieszczając zakładkę przed echo, dla czytelności - knocte
Po prostu umieściłem tę jedną linijkę w funkcji basha, jeśli ktoś tego chce ... github.com/equant/my_bash_tools/blob/master/tarp.bash - equant


Mam funkcję zdefiniowaną w moim .bashrc, która ułatwia to.

command_exists () {
    type "$1" &> /dev/null ;
}

Oto przykład użycia (z mojego .bash_profile.)

if command_exists mvim ; then
    export VISUAL="mvim --nofork"
fi

81
2017-10-14 09:24



Co robi &> zrobić? - Saad Malik
The &>  przekierowuje zarówno stdout, jak i stderr razem. - Josh Strater
Działa tylko z type "$1" > /dev/null 2>&1 - Marcello de Sales
&> może nie być dostępna w Twojej wersji Bash. Kod Marcello powinien działać dobrze; robi to samo. - Josh Strater


To zależy od tego, czy chcesz wiedzieć, czy istnieje w jednym z katalogów w $PATH zmienna lub czy znasz absolutną jej lokalizację. Jeśli chcesz wiedzieć, czy jest w $PATH zmienna, użyj

if which programname >/dev/null; then
    echo exists
else
    echo does not exist
fi

w przeciwnym razie użyj

if [ -x /path/to/programname ]; then
    echo exists
else
    echo does not exist
fi

Przekierowanie do /dev/null/ w pierwszym przykładzie tłumi wyjście pliku which program.


66
2018-02-26 22:01



Naprawdę nie powinieneś używać "które" z powodów opisanych w moim komentarzu. - lhunath


Rozszerzając odpowiedzi @ lhunatha i @ GregV, oto kod dla ludzi, którzy chcą z łatwością umieścić to czek w środku if komunikat:

exists()
{
  command -v "$1" >/dev/null 2>&1
}

Oto jak z niego korzystać:

if exists bash; then
  echo 'Bash exists!'
else
  echo 'Your system does not have Bash'
fi

27
2017-12-07 21:17



Prawie tak niezgrabny, jak to tylko możliwe. Czytać o status wyjścia w man bash i nauczyć się z niego korzystać. Uczyni twój kod znacznie prostszym i bardziej eleganckim. Nawiasy nie są częścią if składni, są po prostu skrótem do polecenia test. if sprawdza, czy polecenie zakończyło się pomyślnie (ma status wyjścia równy 0). - Palec
@Palec: Masz rację, to był dość niezgrabne. Oczyściłem go trochę i mam nadzieję, że teraz będzie bardziej odpowiedni. - Romário
Gotowość do uczenia się i doskonalenia musi być nagradzana. +1 To jest czyste i proste. Jedyne, co mogę dodać, to to command odnosi sukces nawet w przypadku aliasów, co może być nieco sprzeczne z intuicją. Sprawdzanie istnienia w interaktywnej powłoce da różne wyniki po przeniesieniu jej do skryptu. - Palec
Właśnie testowałem i używam shopt -u expand_aliases ignoruje / ukrywa aliasy (np alias ls='ls -F' wymienione w innej odpowiedzi) i shopt -s expand_aliases rozwiązuje je przez command -v. Może więc powinien być ustawiony przed sprawdzeniem i rozbrojony po, chociaż może wpłynąć na wartość zwracaną przez funkcję, jeśli nie przechwycisz i nie zwrócisz wyraźnie wyniku polecenia. - dragon788


Spróbuj użyć:

test -x filename

lub

[ -x filename ]

Od strony bash pod Wyrażenia warunkowe:

 -x file
          True if file exists and is executable.

19
2018-02-26 21:57



Oznacza to, że musisz już znać pełną ścieżkę do aplikacji. - lhunath
OP nie określił, czy chciał sprawdzić konkretną instancję, czy jakąkolwiek wykonywalną instancję ... Odpowiedziałem na nią tak, jak ją przeczytałem. - dmckee


Używać hash, tak jak @lhunath sugeruje, w skrypcie basha:

hash foo &> /dev/null
if [ $? -eq 1 ]; then
    echo >&2 "foo not found."
fi

Ten skrypt działa hash a następnie sprawdza, czy kod zakończenia najnowszego polecenia, wartość przechowywana w $?, jest równe 1. Gdyby hash nie znajduje foo, kod wyjścia będzie 1. Gdyby foo jest obecny, kod wyjścia będzie 0.

&> /dev/null przekierowuje standardowy błąd i standardowe wyjście z hash tak, że nie pojawia się na ekranie i echo >&2 wypisze komunikat do standardowego błędu.


16
2018-06-24 17:01



Dlaczego nie tylko if hash foo &> /dev/null; then ... ? - Beni Cherniavsky-Paskin