Jaka jest różnica między && a | w skrypcie bash?

13

Weźmy dwie linie poniżej, które dają nam dwa różne wyniki.

p=$(cd ~ && pwd) ; echo $p
p=$(cd ~ |  pwd) ; echo $p

Czym się różnią?

Nam G VU
źródło
@heemayl Thanks. Czy możesz wyodrębnić różnice w mojej sprawie? Dzięki.
Nam G VU
1
Wskazówka: polecenia po obu stronach |uruchamiania w podpowłokach.
heemayl

Odpowiedzi:

21

W p=$(cd ~ && pwd):

  • Podstawienie polecenia $(), działa w podpowłoce

  • cd ~zmienia katalog na ~(twój dom), jeśli się cdpowiedzie ( &&), następnie pwdwypisuje nazwę katalogu na STDOUT, stąd zapisany ciąg pbędzie katalogiem domowym np./home/foobar

W p=$(cd ~ | pwd):

  • Ponownie $()pojawia się podpowłoka

  • Komendy po obu stronach |uruchamiania w odpowiednich podpowłokach (i obie rozpoczynają się w tym samym czasie)

  • tak cd ~odbywa się w podpowłoce oraz pwdw oddzielnym podpowłoce

  • więc dostaniesz tylko STDOUT, pwdtj. z miejsca, w którym uruchomisz polecenie, może to być dowolny katalog, jak możesz sobie wyobrazić, a zatem pbędzie on zawierał nazwę katalogu, z którego polecenie jest wywoływane, a nie katalog domowy

heemayl
źródło
Jaki jest cel potoku między poleceniami? cd ~nie generuje żadnych danych wyjściowych i pwdnie odczytuje żadnych danych wejściowych.
Barmar,
Drugie polecenie jest zasadniczo równoważne, (cd ~);p=$(pwd)prawda?
Barmar,
@Barmar Tak, właśnie tego użył OP i właśnie mu to tłumaczę.
heemayl
7

Podstawowym problemem jest sposób, w jaki operatorzy &&i |łączą oba polecenia.

&&Łączy polecenia za pośrednictwem kodu wyjścia. |Łączy dwa polecenia za pomocą deskryptorów (stdin, stdout).

Uprośćmy najpierw. Możemy usunąć zadanie i napisać:

echo $(cd ~ && pwd)
echo $(cd ~ | pwd)

Możemy nawet usunąć podpowłokę wykonania polecenia, aby to przeanalizować:

$ cd ~ && pwd
$ cd ~ | pwd

I &

Jeśli zmienimy monit, aby pokazać katalog, w którym wykonywane są polecenia, coś w PS1='\w\$ 'tym stylu:

/tmp/user$ cd ~ && pwd
/home/user
~$ 
  • Polecenie cd ~zmieniło „obecny katalog” na domową wersję użytkownika, który wykonuje polecenie ( /home/user).
  • Ponieważ wynik polecenia powiódł się (kod wyjścia 0), następne polecenie po wykonaniu && zostanie wykonane
  • I drukowany jest „obecny katalog roboczy”.
  • Działająca powłoka zmieniła swoją pwdna, ~jak pokazuje monit ~$.

Jeśli z jakiegoś powodu zmiana katalogu nie powiodła się (kod wyjścia nie wynosi 0) (katalog nie istnieje, blok uprawnień odczytu katalogu) następna komenda nie zostanie wykonana.

Przykład:

/tmp/user$ false && pwd
/tmp/user$ _ 

Kod wyjścia 1 z falseuniemożliwia wykonanie następnego polecenia.

Zatem kod wyjścia „polecenia 1” wpływa na „polecenie 2”.

Teraz efekty całego polecenia:

/tmp/user$ echo $(cd ~ && pwd)
/home/user
/tmp/user$ _

Katalog został zmieniony, ale wewnątrz podpowłoki $(…)zmieniony katalog jest drukowany /home/user, ale jest natychmiast odrzucany po zamknięciu podpowłoki. Pwd wraca do katalogu początkowego ( /tmp/user).

|

Oto co się dzieje:

/tmp/user$ cd ~ | pwd
/tmp/user
/tmp/user$ _

Metaznak |(nie prawdziwy operator) sygnalizuje powłoce utworzenie „potoku”, (w skrócie) każde polecenie po każdej stronie potoku ( |) jest ustawione wewnątrz każdej własnej podpowłoki, najpierw po prawej stronie polecenie, a następnie lewy. Deskryptor pliku wejściowego ( /dev/stdin) prawego polecenia jest połączony z deskryptorem wyjściowym ( /dev/stdout), a następnie oba polecenia są uruchamiane i pozostawione do interakcji. Lewe polecenie ( cd -) nie ma danych wyjściowych, a także prawe polecenie ( pwd) nie przyjmuje danych wejściowych. Tak więc każdy działa niezależnie w każdej własnej podpowłoce.

  • cd ~Zmienia pwd jednej powłoce.
  • pwdDrukuje (PWD) całkowicie niezależne od innych pod-obudowy.

Zmiany w każdej powłoce są odrzucane, gdy rura się kończy, zewnętrzna podpowłoka nie zmieniła PWD.

Dlatego te dwa polecenia są połączone tylko przez „deskryptory plików”.
W tym przypadku nic nie jest wysyłane i nic nie jest czytane.

Całe polecenie:

$ echo "$(cd ~ | pwd)"

Po prostu wydrukuje katalog, w którym polecenie zostało wykonane.

Izaak
źródło
5

Nie jestem pewien, czy miałeś na myśli „|” lub „||” w twoim drugim przypadku.

„|” w powłoce potokuje wyjście jednego polecenia na wejście innego - częstym przypadkiem użycia jest coś takiego: curl http://abcd.com/efgh | grep ijkl np. uruchom polecenie i użyj innego polecenia, aby przetworzyć wynik polecenia.

W podanym przykładzie jest to dość nonsensowne, ponieważ „cd” zwykle nie generuje żadnych danych wyjściowych, a „pwd” nie oczekuje żadnych danych wejściowych.

„&&” i „||” są jednak poleceniami partnerów. Są zaprojektowane tak, aby można było z nich korzystać w taki sam sposób, jak operatory logiczne „i” i „lub” w większości języków. Jednak przeprowadzane optymalizacje nadają im określone zachowanie, które jest paradygmatem programowania powłoki.

Aby określić wynik operacji logicznej „i”, należy ocenić drugi warunek tylko wtedy, gdy pierwszy warunek się powiedzie - jeśli pierwszy warunek się nie powiedzie, ogólny wynik zawsze będzie fałszywy.

Aby określić wynik operacji logicznej „lub”, musisz ocenić drugi warunek tylko wtedy, gdy pierwszy warunek się nie powiedzie - jeśli pierwszy warunek się powiedzie, ogólny wynik zawsze będzie prawdziwy.

Tak więc w powłoce, jeśli ją wykonasz, command1 && command2 command2zostanie wykonany dopiero po command1zakończeniu i zwróceniu pomyślnego kodu wynikowego. Jeśli masz, command1 || command2 command2zostanie wykonany po command1zakończeniu, jeśli command1zwróci kod błędu.

Innym powszechnym paradygmatem jest command1komenda testowa - generuje ona pojedynczą instrukcję if / then - na przykład:

[ "$VAR" = "" ] && VAR="Value if empty"

Jest (długo rozwiniętym) sposobem przypisywania wartości zmiennej, jeśli jest ona obecnie pusta.

Istnieje wiele przykładów zastosowania tego procesu w innym miejscu na Stack Exchange

Michael Firth
źródło