Jaka jest różnica między <<
, <<<
a < <
w bash?
Jaka jest różnica między <<
, <<<
a < <
w bash?
Odpowiedzi:
Tutaj dokument
<<
jest znany jako here-document
struktura. Dajesz programowi znać, co będzie końcowym tekstem, i za każdym razem, gdy zobaczysz ten ogranicznik, program odczyta wszystkie dane, które przekazałeś programowi jako dane wejściowe, i wykona na nim zadanie.
Oto co mam na myśli:
$ wc << EOF
> one two three
> four five
> EOF
2 5 24
W tym przykładzie mówimy wc
programowi, aby czekał na EOF
ciąg znaków, a następnie wpisz pięć słów, a następnie wpisz, EOF
aby zasygnalizować, że wprowadzono dane wejściowe. W rzeczywistości jest podobny do biegania wc
samemu, wpisywania słów, a następnie naciskaniaCtrlD
W bash są one implementowane za pomocą plików tymczasowych, zwykle w formie /tmp/sh-thd.<random string>
, natomiast w dash są implementowane jako anonimowe potoki. Można to zaobserwować poprzez śledzenie wywołań systemowych za pomocą strace
polecenia. Wymień bash
się sh
, aby zobaczyć, jak /bin/sh
wykonuje to przekierowanie.
$ strace -e open,dup2,pipe,write -f bash -c 'cat <<EOF
> test
> EOF'
Tutaj ciąg
<<<
jest znany jako here-string
. Zamiast wpisywać tekst, podajesz programowi gotowy ciąg tekstu. Na przykład przy takim programie, bc
jaki możemy zrobić, bc <<< 5*4
aby uzyskać dane wyjściowe dla tego konkretnego przypadku, nie trzeba interakcyjnie uruchamiać bc.
Tutaj ciągi w bash są implementowane za pomocą plików tymczasowych, zwykle w formacie /tmp/sh-thd.<random string>
, które później są rozłączane, dzięki czemu zajmują tymczasowo pewną przestrzeń pamięci, ale nie pojawiają się na liście /tmp
pozycji katalogu i skutecznie istnieją jako pliki anonimowe, które mogą nadal odwołuje się do deskryptora pliku przez samą powłokę, a ten deskryptor pliku jest dziedziczony przez polecenie, a następnie powielany na deskryptorze pliku 0 (stdin) przez dup2()
funkcję. Można to zaobserwować poprzez
$ ls -l /proc/self/fd/ <<< "TEST"
total 0
lr-x------ 1 user1 user1 64 Aug 20 13:43 0 -> /tmp/sh-thd.761Lj9 (deleted)
lrwx------ 1 user1 user1 64 Aug 20 13:43 1 -> /dev/pts/4
lrwx------ 1 user1 user1 64 Aug 20 13:43 2 -> /dev/pts/4
lr-x------ 1 user1 user1 64 Aug 20 13:43 3 -> /proc/10068/fd
I poprzez śledzenie wywołań systemowych (dane wyjściowe skrócone dla czytelności; zwróć uwagę, jak plik tymczasowy jest otwierany jako fd 3, dane do niego zapisywane, następnie jest ponownie otwierany z O_RDONLY
flagą jako fd 4, a następnie odłączony, a następnie dup2()
na fd 0, który jest dziedziczony cat
później) ):
$ strace -f -e open,read,write,dup2,unlink,execve bash -c 'cat <<< "TEST"'
execve("/bin/bash", ["bash", "-c", "cat <<< \"TEST\""], [/* 47 vars */]) = 0
...
strace: Process 10229 attached
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
[pid 10229] write(3, "TEST", 4) = 4
[pid 10229] write(3, "\n", 1) = 1
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDONLY) = 4
[pid 10229] unlink("/tmp/sh-thd.uhpSrD") = 0
[pid 10229] dup2(4, 0) = 0
[pid 10229] execve("/bin/cat", ["cat"], [/* 47 vars */]) = 0
...
[pid 10229] read(0, "TEST\n", 131072) = 5
[pid 10229] write(1, "TEST\n", 5TEST
) = 5
[pid 10229] read(0, "", 131072) = 0
[pid 10229] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10229, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
Opinia: potencjalnie ponieważ tutaj łańcuchy wykorzystują tymczasowe pliki tekstowe, jest to możliwy powód, dla którego tutaj łańcuchy zawsze wstawiają końcowy znak nowej linii, ponieważ plik tekstowy według definicji POSIX musi mieć linie kończące się znakiem nowego wiersza.
Zastąpienie procesu
Jak wyjaśnia tldp.org ,
Podstawienie procesu podaje dane wyjściowe procesu (lub procesów) do standardowego wejścia innego procesu.
W efekcie jest to podobne do przesyłania potokiem jednego polecenia do drugiego, np echo foobar barfoo | wc
. Ale zauważ: na stronie bash zobaczysz, że jest oznaczony jako <(list)
. Zasadniczo możesz przekierować dane wyjściowe wielu (!) Poleceń.
Uwaga: technicznie rzecz biorąc, gdy mówisz, < <
że nie masz na myśli jednej rzeczy, ale dwa przekierowania z pojedynczym <
i procesowym przekierowaniem wyjścia z <( . . .)
.
Co się stanie, jeśli dokonamy tylko zamiany?
$ echo <(echo bar)
/dev/fd/63
Jak widać, powłoka tworzy tymczasowy deskryptor pliku, do /dev/fd/63
którego trafia dane wyjściowe (co według odpowiedzi Gillesa jest anonimową potokiem). Oznacza to, <
że przekierowuje ten deskryptor pliku jako dane wejściowe do polecenia.
Tak bardzo prostym przykładem może być zamiana procesu podstawiania danych wyjściowych z dwóch poleceń echa na wc:
$ wc < <(echo bar;echo foo)
2 2 8
Więc tutaj zmuszamy powłokę do utworzenia deskryptora pliku dla wszystkich danych wyjściowych w nawiasach i przekierowania, które jako dane wejściowe do wc
. Jak oczekiwano, wc odbiera ten strumień z dwóch poleceń echa, które same z siebie wyprowadziłyby dwie linie, z których każda zawiera słowo, i odpowiednio mamy 2 słowa, 2 linie i 6 znaków plus dwie nowe linie.
Uwaga dodatkowa: Podstawienie procesu może być określane jako bashism (polecenie lub struktura użyteczne w zaawansowanych powłokach takich jak bash
, ale nie określone przez POSIX), ale zostało zaimplementowane ksh
przed istnieniem basha jako strona podręcznika użytkownika ksh i ta odpowiedź sugeruje. Powłoki jak tcsh
i mksh
jednak nie mają substytucji procesu. Jak więc moglibyśmy przekierowywać wyjście wielu poleceń do innego polecenia bez zastępowania procesu? Grupowanie plus orurowanie!
$ (echo foo;echo bar) | wc
2 2 8
W rzeczywistości jest to to samo co w powyższym przykładzie, jednak różni się to pod maską od zastępowania procesu, ponieważ wykonujemy stdout całej podpowłoki i stdin wc
połączony z rurą . Z drugiej strony podstawienie procesu powoduje, że polecenie czyta tymczasowy deskryptor pliku.
Jeśli więc możemy grupować za pomocą pipingu, dlaczego potrzebujemy podstawiania procesów? Ponieważ czasami nie możemy używać rur. Rozważ poniższy przykład - porównanie wyników dwóch poleceń z diff
(które wymagają dwóch plików, w tym przypadku podajemy dwa deskryptory plików)
diff <(ls /bin) <(ls /usr/bin)
< <
jest używany, gdy otrzymuje się standardowe wejście z podstawienia procesu . Takie polecenie może wyglądać tak: cmd1 < <(cmd2)
. Na przykładwc < <(date)
< <
nie jest rzeczą samą w sobie, w przypadku substytucji procesu <
następuje po niej coś innego, co zaczyna się od<
<<<
najpierw został zaimplementowany przez port uniksowy powłoki Plan 9 rc, a następnie przyjęty przez zsh, bash i ksh93. Nie nazwałbym tego bazmem.
echo 'foo' | read; echo ${REPLY}
będzie nie powróci foo
, ponieważ read
jest rozpoczynane w podpowłoce - orurowanie rozpoczyna podpowłoce. Jednak read < <(echo 'foo'); echo ${REPLY}
poprawnie zwraca foo
, ponieważ nie ma podpowłoki.
< <
jest błędem składni:
$ cat < <
bash: syntax error near unexpected token `<'
< <()
to proces podstawiania ( <()
) w połączeniu z przekierowaniem ( <
):
Przemyślany przykład:
$ wc -l < <(grep ntfs /etc/fstab)
4
$ wc -l <(grep ntfs /etc/fstab)
4 /dev/fd/63
W przypadku podstawienia procesu ścieżka do deskryptora pliku jest używana jak nazwa pliku. W przypadku, gdy nie chcesz (lub nie możesz) bezpośrednio użyć nazwy pliku, łączysz podstawienie procesu z przekierowaniem.
Żeby było jasne, nie ma < <
operatora.
<()
daje nazwę podobną do nazwy pliku, więc jest bardziej użyteczna - < <()
zastępuje stdin tam, gdzie może nie być konieczne. W wc
tym drugim przypadku okazuje się bardziej przydatne. Może być mniej przydatny gdzie indziej
< <
to błąd składniowy, prawdopodobnie masz na myśli command1 < <( command2 )
proste przekierowanie wejścia, po którym następuje podstawienie procesu i jest bardzo podobne, ale nie równoważne z:
command2 | command1
Różnica przy założeniu, że działasz, bash
jest command1
uruchamiana w podpowłoce w drugim przypadku, podczas gdy jest uruchamiana w bieżącej powłoce w pierwszym. Oznacza to, że ustawione zmienne command1
nie zostaną utracone w wariancie substytucji procesu.
< <
da błąd składniowy. Właściwe użycie jest następujące:
Wyjaśnienie za pomocą przykładów:
Przykład dla < <()
:
while read line;do
echo $line
done< <(ls)
W powyższym przykładzie dane wejściowe do pętli while będą pochodzić z ls
polecenia, które można odczytać wiersz po wierszu i echo
edytować w pętli.
<()
służy do podstawienia procesu. Więcej informacji i przykład <()
można znaleźć pod tym linkiem: