Czy test lub [lub [[jest bardziej przenośny zarówno pomiędzy powłokami bash, jak i między innymi powłokami?


44

Widzę, że mogę

$ [ -w /home/durrantm ] && echo "writable"
writable

lub

$ test -w /home/durrantm && echo "writable"
writable

lub

$ [[ -w /home/durrantm ]] && echo "writable"
writable

Lubię używać trzeciej składni. Czy są one równoważne pod każdym względem i dla wszystkich przypadków negatywnych i skrajnych? Czy są jakieś różnice w przenośności, np. Między bash na Ubuntu i OS X lub starszych / nowszych wersjach bash, np. Przed / po 4.0 i czy oba rozszerzają wyrażenia w ten sam sposób?


2
W [ … ]porównaniu do [[ … ]]vs test …, w tym najczęściej duplikowanym pytaniu są bardziej kompletne odpowiedzi .
Gilles „SO- przestań być zły”

Aby zapoznać się z konkretnym problemem testowania zapisu pliku, zobacz także Jak nieinwazyjnie przetestować dostęp do zapisu pliku?
G-Man mówi „Przywróć Monikę”

1
Jest takie powiedzenie: „nie ma przenośnego kodu, tylko przeniesiony kod”. Moja rada w tej sprawie: użyj najbardziej czytelnej formy (prawdopodobnie [[...]]) i wypróbuj ją na wszystkich platformach, które chcesz obsługiwać. Ukrywanie skryptów nie ma większego sensu, więc działają na starożytnych platformach, z których ani ty, ani twoi odbiorcy docelowi nie korzystają. Uczyni to twój kod trudnym do odczytania, wprowadzi niepotrzebne błędy, a może nawet problemy z bezpieczeństwem (tak jak w przypadku openssl).
stefan.schwetschke

Odpowiedzi:


31

[jest synonimem testpolecenia i jednocześnie jest wbudowanym bashem i osobnym poleceniem. Ale [[jest słowem kluczowym bash i działa tylko w niektórych wersjach. Dlatego ze względu na przenośność lepiej jest używać pojedynczego []lubtest

[ -w "/home/durrantm" ] && echo "writable"

2
[jest wbudowany POSIX czy Bash?
Sandburg,

@Sandburg POSIX jest standardem, więc nie może mieć wbudowanych funkcji. Nie określa, czy coś ma być wbudowane w interpretera, czy nie, tylko to, co ma zrobić. Zasady te dotyczą zarówno formularzy, jak testi [podobnych. Jeśli powłoka może wykonać ocenę samodzielnie, oszczędza to proces i czyni go nieco szybszym, ale wynik musi być taki sam.
Bachsau

@Bachsau, który wciąż nie odpowiada na pytanie Sandburga, czy zachowanie [jest zdefiniowane przez POSIX i dlatego jest maksymalnie przenośne (co wydaje się być
sednem

@ Sandburg (i każdemu innemu, kto natknie się na to): Tak, wygląda na oba [i test są POSIX . Wydaje się, że nie ma „zalecanego”, choć jedyną różnicą (?), Którą mogę między nimi zauważyć, jest to, że test„nie rozpoznaję argumentu„ - ”[jako ogranicznika wskazującego koniec opcji]” (? - sugerując, że „ - „ jest rozpoznawany jako argument za końcem argumentów, dla [którego i tak nie mogę wymyślić dobrego przypadku testowego. Ale dygresuję. Użyj tego, co zrobisz. IMO, [wygląda elegancko, ale testnajwyraźniej jest poleceniem)
JamesTheAwesomeDude

35

Tak, są różnice. Najbardziej przenośne są testlub [ ]. Oba są częścią specyfikacji POSIXtest .

if ... fiKonstrukt jest również zdefiniowane przez POSIX i powinny być w pełni przenośne.

[[ ]]To kshcecha, która jest również obecny w niektórych wersjach bash( wszystkie te nowoczesne ), w zshbyć może w innych, ale nie jest obecna w shlub dashlub różnych innych prostszych muszli.

Tak, aby skrypty przenośny, użyj [ ], testlub if ... fi.


10
Wystarczy wspomnieć bashi zshwspierałem [[przez bardzo długi czas ( bashdodałem go pod koniec lat 90., zshnie później niż w 2000 r., I byłbym zaskoczony, gdyby kiedykolwiek brakowało wsparcia), więc jest mało prawdopodobne, abyś spotkał się z wersją którejkolwiek z nich [[. Wystąpienie innej powłoki zgodnej z POSIX (takiej jak dash) jest znacznie bardziej prawdopodobne.
chepner

1
Jedną z interesujących cech [[jest to, że rozszerzenia parametrów nie muszą być cytowane: [[-f $file]]zdarzenie działa, jeśli $filezawiera znaki spacji.
helpermethod

2
@helpermethod również, wbudowane wyrażenia regularne[[ $a =~ ^reg.*exp.*$' ]]
GnP

20

Pamiętaj, że [] && cmdto nie to samo, co if .. fikonstrukcja.

Czasami jego zachowanie jest dość podobne i można go użyć [] && cmdzamiast if .. fi. Ale tylko czasami. Jeśli masz więcej niż jedno polecenie do wykonania, jeśli warunek lub musisz if .. else .. fizachować ostrożność i sprawdzić logikę.

Kilka przykładów:

[ -z "$VAR" ] && ls file || echo wiiii

To nie to samo co

if [ -z $VAR ] ; then
  ls file
else
  echo wiii
fi

ponieważ jeśli lszawiedzie, echozostanie wykonane, co się nie stanie if.

Inny przykład:

[ -z "$VAR" ] && ls file && echo wiii

to nie to samo co

if [ -z "$VAR" ] ; then
   ls file
   echo $wiii
fi

chociaż ta konstrukcja będzie działać tak samo

[ -z "$VAR" ] && { ls file ; echo wiii ; }

Uwaga: ;echo jest ważne i musi tam być.

Wracając do powyższego stwierdzenia, możemy powiedzieć

[] && cmd == jeśli pierwsze polecenie zakończy się powodzeniem, wykonaj następne

if .. fi == jeśli warunek (który może być również poleceniem testowym), wówczas wykonaj polecenia

Więc dla przenośności pomiędzy [i tylko do [[użytku [.

ifjest kompatybilny z POSIX. Więc jeśli musisz wybierać pomiędzy [i ifpatrzeć na swoje zadanie i oczekiwane zachowanie.


Prawdopodobnie jestem po prostu gęsty, ale nie widzę, jaka byłaby różnica ... czy mógłbyś podać przykład, w którym te dwie konstrukcje dawałyby różne wyniki?
evilsoup

1
@evilsoup, zaktualizowano. Być może nie jestem najlepszym tłumaczem, choć mam nadzieję, że teraz będzie jasne.
pędzi

1
Bardzo dobre punkty, pośpiech. Przypuszczam bezpieczną formą swojej 1st przykład byłoby: [ -z "$VAR" ] && { ls file; true; } || echo wiiii. Jest nieco bardziej gadatliwy, ale wciąż krótszy niż if...fikonstrukcja.
PM 2, dzwoni

Sprawiło, że pytanie dotyczyło [vs [[vs test], a nie także tego, czy ... fi vs &&, aby było JEDNYM pytaniem. Niestety zrobienie tego sprawia, że ​​ta odpowiedź wydaje się nie na miejscu. Przepraszamy za to, że początkowo nie skupiałem się na pytaniu, które do tego doprowadziło. Żyj i ucz się. :)
Michael Durrant

Głosowałem negatywnie, ponieważ a) jest to głównie dobra odpowiedź, ale jest to odpowiedź na inne pytanie. b) prezentujesz [i ifjako podtytuły, ale nie są. To tak naprawdę &&zastępuje if. [wykonuje kod i zwraca status, podobnie jak lsi grepzrobiłby. ifwykonywanie rozgałęzień w zależności od statusu zwracanego polecenia (instrukcji) podanego po if, może to być dowolne polecenie (instrukcja). &&wykonuje następną instrukcję tylko wtedy, gdy poprzednia zwróciła 0, podobnie jak proste if..then..fi.
GnP

9

W rzeczywistości to, &&co zastępuje if, a nie test: ifinstrukcja w skrypcie powłoki sprawdza, czy polecenie zwróciło „pomyślny” (zerowy) status wyjścia; w twoim przykładzie polecenie to [.

Tak więc różnią się tutaj dwie rzeczy: polecenie użyte do uruchomienia testu oraz składnia użyta do wykonania kodu na podstawie wyniku tego testu.

Polecenia testowe:

  • testjest znormalizowanym poleceniem do oceny właściwości ciągów i plików; w twoim przykładzie uruchamiasz polecenietest -w /home/durrantm
  • [jest aliasem tego polecenia, jednakowo znormalizowanym, który ma obowiązkowy ostatni argument ], aby wyglądał jak wyrażenie w nawiasach; nie daj się zwieść, to wciąż tylko polecenie (możesz nawet stwierdzić, że twój system ma plik o nazwie /bin/[)
  • [[jest rozszerzoną wersją polecenia testowego wbudowaną w niektóre powłoki, ale nie jest częścią tego samego standardu POSIX; zawiera dodatkowe opcje, których nie używasz tutaj

Wyrażenia warunkowe:

  • &&Uruchamiający ( znormalizowane o ) wykonuje operację logiczną AND, oceniając dwa polecenia i powrót 0 (który stanowi prawdziwy), jeżeli oba return 0; oceni drugie polecenie tylko wtedy, gdy pierwsze zwróciło zero, więc można go użyć jako zwykłego warunkowego
  • if ... then ... fiKonstrukt ( standaryzowany tutaj ) wykorzystuje tę samą metodę oceny „prawdę”, ale pozwala na liście złożonych oświadczeń w thenklauzuli, zamiast jednego polecenia zapewnianej przez &&zwarciem i zapewnia elifi elseklauzule, które są trudne do napisania używając tylko &&i ||. Zauważ, że nie ma nawiasów wokół warunku w ifinstrukcji .

Wszystkie poniższe elementy są jednakowo przenośne i całkowicie równoważne, renderingi Twojego przykładu:

  • test -w /home/durrantm && echo "writable"
  • [ -w /home/durrantm ] && echo "writable"
  • if test -w /home/durrantm; then echo "writable"; fi
  • if [ -w /home/durrantm ]; then echo "writable"; fi

Chociaż poniższe są również równoważne, ale mniej przenośne z powodu niestandardowego charakteru [[:

  • [[ -w /home/durrantm ]] && echo "writable"
  • if [[ -w /home/durrantm ]]; then echo "writable"; fi

Tak, usunąłem część if .... fi, aby było to jedno pytanie.
Michael Durrant

7

Aby przenieść, użyj test/ [. Ale jeśli nie potrzebujesz przenośności, ze względu na zdrowie psychiczne siebie i innych osób korzystających z twojego skryptu [[. :)

Zobacz także What is the difference between test, [ and [[ ?w BashFAQ .


7

Jeśli chcesz mieć przenośność poza światem podobnym do Bourne'a:

test -w /home/durrantm && echo writable

jest najbardziej przenośny. Działa w skorupkach Bourne'a cshi rcrodzinach.

test -w /home/durrantm && echo "writable"

Byłoby wyjście "writable"zamiast writablew muszli z rcrodziną ( rc, es, akanga, gdzie "nie ma specjalnego).

[ -w /home/durrantm ] && echo writable

nie działałby w powłokach rodzin cshlub rcw systemach, w których nie ma [polecenia $PATH(wiadomo, że niektóre mają, testale nie mają [aliasu).

if [ -w /home/durrantm ]; then echo writabe; fi

działa tylko w skorupach rodziny Bourne.

[[ -w /home/durrantm ]] && echo writable

działa tylko w ksh(skąd pochodzi) zshi bash(wszystkie 3 w rodzinie Bourne).

Żadne nie działałoby w fishpowłoce, w której potrzebujesz:

[ -w /home/durrantm ]; and echo writable

lub:

if [ -w /home/durrantm ]; echo writable; end

0

Mój najważniejszy powodem wyboru albo if foo; then bar; ficzy foo && barjest czy kod zakończenia całego polecenia jest ważne.

porównać:

#!/bin/sh
set -e
foo && bar
do_baz

z:

#!/bin/sh
set -e
if foo; then bar; fi
do_baz

Możesz pomyśleć, że robią to samo; jeśli jednak foozawiedzie (lub jest fałszywe, w zależności od twojego punktu widzenia), to w pierwszym przykładzie do_baz nie zostanie wykonany, ponieważ skrypt zostanie zakończony ... set -eInstruuje powłokę, aby natychmiast zakończyła działanie, jeśli jakakolwiek komenda zwróci fałszywy status. Bardzo przydatne, jeśli robisz rzeczy takie jak:

cd /some/directory
rm -rf *

Nie chcesz, aby skrypt kontynuował działanie, jeśli wystąpi błąd z cdjakiegokolwiek powodu.


1
Żadna awaria nie foospowoduje przerwania skryptu w obu przypadkach. Jest to szczególny przypadek dla set -e(gdy polecenie jest oceniane jako warunek (po lewej stronie niektórych && / || lub w if / while / till / elsif ...). Niepowodzenie opuściłoby barpowłokę w obu przypadkach.
Stéphane Chazelas

2
Chcesz cd /some/directory && rm -rf -- *lub cd /some/directory || exit; rm -rf -- *(nadal nie usuwa ukrytych plików). Osobiście nie podoba mi się pomysł, set -eaby nie usprawiedliwiać się pisaniem poprawnego kodu.
Stéphane Chazelas,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.