Dlaczego niektóre polecenia nie czytają ze standardowego wejścia?

19

Zastanawiam się, kiedy powinniśmy korzystać z rurociągów, a kiedy nie.

Powiedzmy na przykład, aby zabić pewien proces, który obsługuje pliki pdf, następujące działania nie będą działać przy użyciu potoku:

ps aux | grep pdf | awk '{print $2}'|kill

Zamiast tego możemy to zrobić tylko na następujące sposoby:

kill $(ps aux| grep pdf| awk '{print $2}')

lub

ps aux | grep pdf | awk '{print $2}'| xargs kill

Według man bash(wersji 4.1.2):

The standard output of command is connected via a pipe to the standard input of command2.

Dla powyższego scenariusza:

  • stdin grepjest stdout ps. To działa.
  • stdin awkjest stdout grep. To działa.
  • stdin killjest stdout awk. To nie działa.

Stdin następującego polecenia zawsze pobiera dane wejściowe ze standardowego wejścia poprzedniego polecenia.

  • Dlaczego to nie działa z killlub rm?
  • Co to jest różnie kill, rmwejście z grep, awkwejście?
  • Czy są jakieś zasady?
sylye
źródło
1
To nie jest odpowiedź, ale warto rzucić okiem na tych pgrep, pkilli killallpoleceń.
terdon
2
@terdon: Używam powyższego scenariusza, aby pokazać problem z rurociągiem, rozumiem to, pgrepa reszta może to doskonale osiągnąć :)
sylye

Odpowiedzi:

17

Istnieją dwa typowe sposoby dostarczania danych wejściowych do programów:

  • dostarczać dane do STDIN procesów
  • podaj argumenty wiersza poleceń

killużywa tylko argumentów wiersza poleceń. Nie czyta ze STDIN. Programy takie jak grepi awkczytane ze STDIN (jeśli nie podano nazw plików jako argumentów wiersza poleceń) i przetwarzają dane zgodnie z ich argumentami wiersza poleceń (wzorzec, instrukcje, flagi, ...).

Można tylko potokować do STDIN innych procesów, a nie do argumentów wiersza poleceń.

Powszechną zasadą jest, że programy używają STDIN do przetwarzania dowolnej ilości danych. Wszystkie dodatkowe parametry wejściowe lub, jeśli zwykle jest ich niewiele, przekazywane są przez argumenty wiersza poleceń. Jeśli wiersz poleceń może być bardzo długi, na przykład w przypadku długich awktekstów programów, często istnieje możliwość odczytania ich z dodatkowych plików programu ( -fopcja awk).

Aby użyć STDOUT programów jako argumentów wiersza poleceń, użyj $(...)lub w przypadku dużej ilości danych xargs. findmożna to również bezpośrednio z -exec ... {} +.

Dla kompletności: Aby zapisać argumenty wiersza poleceń do STDOUT, użyj echo.

Jofel
źródło
1
Skąd wiemy, że polecenie przyjmuje tylko argumenty, ale nie STDIN? Czy istnieje sposób systematyczny lub programowy zamiast zgadywania lub czytania ze strony podręcznika? Czytając tylko stronę podręcznika, nie udało mi się uzyskać żadnych konkretnych wskazówek, czy polecenie może przyjmować STDIN, ponieważ STDIN jest również częścią argumentów przedstawionych na stronie podręcznika. Na przykład gzipw SYNOPSIS nie powiedziano, że musi on przyjmować NAZWĘ PLIKU jako dane wejściowe. Szukam, czy istnieje bardziej systematyczny sposób, aby to ustalić.
sylye
Istnieje również argument „-”, który oznacza „stdin” (lub „stdout”) dla niektórych poleceń.
Emmanuel
Nie xargspozwoli ci dokładnie „sprowadzać argumentów do wiersza poleceń”?
T. Verron
@ T.Verron tak, to jest zadanie xargs. W razie potrzeby wywołuje polecenie więcej niż jeden raz (rozmiar wiersza polecenia jest ograniczony) i ma wiele innych opcji.
jofel
2
Tekst opisu opisuje sposób korzystania z programu. Na przykład gzip mówi: „Program gzip kompresuje i dekompresuje pliki przy użyciu kodowania Lempel-Ziv (LZ77). Jeśli nie określono żadnych plików, gzip będzie kompresował ze standardowego wejścia lub dekompresował na standardowe wyjście”. Jeśli strona podręcznika nie wspomina o standardowym wejściu, nie będzie go używać.
Alan Shutko
16

To interesujące pytanie, które dotyczy części filozofii Unix / Linux.

Więc jaka jest różnica między programami takimi jak grep, sed, sortz jednej strony, a kill, rm, lsz drugiej strony? Widzę dwa aspekty.

Filtr aspekt

  • Pierwszy rodzaj programów nazywa się również filtrami . Pobierają dane wejściowe z pliku lub ze STDIN, modyfikują je i generują dane wyjściowe, głównie do STDOUT. Są przeznaczone do użycia w potoku z innymi programami jako źródłami i miejscami docelowymi.

  • Drugi rodzaj programów działa na dane wejściowe, ale dane wyjściowe, które dają, często nie są powiązane z danymi wejściowymi. killnie ma wyjścia, gdy działa regularnie, podobnie jak nie ls. Po prostu mają wartość zwracaną, aby pokazać sukces. Zwykle nie pobierają danych wejściowych ze STDIN, ale przeważnie przekazują dane wyjściowe do STDOUT.

W przypadku takich programów lsaspekt filtrowania nie działa tak dobrze. Z pewnością może mieć wejście (ale go nie potrzebuje), a wyjście jest ściśle powiązane z tym wejściem, ale nie działa jako filtr. Jednak w przypadku tego rodzaju programów drugi aspekt nadal działa:

Semantyczny aspekt

  • W przypadku filtrów ich wejście nie ma znaczenia semantycznego . Po prostu odczytują dane, modyfikują dane, dane wyjściowe. Nie ma znaczenia, czy jest to lista wartości liczbowych, niektórych nazw plików czy kodu źródłowego HTML. Znaczenie tych danych jest podane tylko przez kod, który podajesz do filtra: wyrażenie regularne dla grep, reguły awklub program Perla.

  • W przypadku innych programów, takich jak killlub ls, ich wprowadzanie ma znaczenie , oznaczenie . killoczekuje numerów procesów, lsoczekuje nazw plików lub ścieżek. Nie mogą obsługiwać dowolnych danych i nie są przeznaczone do tego. Wiele z nich nie potrzebuje nawet żadnych danych wejściowych ani parametrów ps. Zwykle nie czytają ze STDIN.

Prawdopodobnie można by połączyć te dwa aspekty: Filtr to program, którego dane wejściowe nie mają znaczenia semantycznego dla programu.

Jestem pewien, że gdzieś przeczytałem o tej filozofii, ale w tej chwili nie pamiętam żadnych źródeł, przepraszam. Jeśli ktoś ma jakieś źródła, możesz je edytować.

Dubu
źródło
5

Nie ma „zasad” jako takich. Niektóre programy pobierają dane ze STDIN, a niektóre nie. Jeśli program może pobierać dane wejściowe ze STDIN, może być przesyłany potokowo, jeśli nie, nie może.

Zwykle możesz stwierdzić, czy program pobierze dane, myśląc o tym, co robi. Jeśli praca programu jest w jakiś sposób manipulować zawartość z pliku (np grep, sed, awketc.), to zwykle trwa wejściowe z stdin. Jeśli jego zadaniem jest manipulowanie sam plik (np mv, rm, cp) lub proces (np kill, lsof) lub powrotnej do informacji o czymś (np top, find, ps), to nie.

Innym sposobem myślenia na ten temat jest różnica między argumentami a danymi wejściowymi. Na przykład:

mv foo bar

W powyższym poleceniu mvnie ma danych wejściowych jako takich. Podano dwa argumenty. Nie wie ani nie obchodzi, co jest w żadnym z plików, po prostu wie, że to są jego argumenty i powinien nimi manipulować.

Z drugiej strony

sed -e 's/foo/bar/' < file
--- -- ------------   ----
 |   |       |          |-> input
 |   |       |------------> argument        
 |   |--------------------> option/flag/switch
 |------------------------> command

Tutaj sedpodano dane wejściowe oraz argument. Ponieważ pobiera dane wejściowe, może je odczytać ze STDIN i może być przesyłane potokowo.

Staje się bardziej skomplikowane, gdy argumentem może być dane wejściowe. Na przykład

cat file

Oto fileargument, który został podany cat. Mówiąc ściślej, argumentem jest nazwa pliku file. Ponieważ jednak catjest to program, który manipuluje zawartością plików, jego wejściem jest wszystko, co jest w środku file.

Można to zilustrować za stracepomocą programu, który śledzi wywołania systemowe wykonywane przez procesy. Jeśli uruchomimy cat fooprzez strace, możemy zobaczyć, że plik foojest otwarty:

$ strace cat foo 2| grep foo
execve("/bin/cat", ["cat", "foo"], [/* 44 vars */]) = 0
open("foo", O_RDONLY)     

Pierwszy wiersz powyżej pokazuje, że program /bin/catzostał wywołany, a jego argumentami były cati foo(pierwszym argumentem jest zawsze sam program). Później argument foozostał otwarty w trybie tylko do odczytu. Teraz porównaj to z

$ strace ls foo 2| grep foo 
execve("/bin/ls", ["ls", "foo"], [/* 44 vars */]) = 0
stat("foo", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lstat("foo", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
write(1, "foo\n", 4foo

Tutaj także lswziął siebie i foojako argumenty. Jednak nie ma openwywołania, argument nie jest traktowany jako dane wejściowe. Zamiast tego lswywołuje statbibliotekę systemową (co nie jest tym samym co statpolecenie), aby uzyskać informacje o pliku foo.

Podsumowując, jeśli uruchomione polecenie odczyta jego dane wejściowe, możesz przesłać do niego potokiem, jeśli nie, nie możesz.

terdon
źródło
0
  • Dlaczego nie działa z kill lub rm?

killi rmnie potrzebujesz STDIN.

  • Jaka jest różnica między wprowadzaniem kill, rm i grep, awk?

W przypadku killi rmużytkownicy podają swoje niestandardowe informacje jako argument i $(cmd)pomaga wziąć STDOUT cmdi przekonwertować go na argument.

Dla grepi awkużytkownicy podają argumenty, a ponadto STDINlub zwykły plik, który będzie przetwarzany przez polecenie. STDINmożna przekazać za pomocą potoku |lub ręcznie wprowadzając.

  • Czy są jakieś zasady?

Przeczytaj instrukcję lub kody źródłowe. A jeśli nie znajdziesz nic, czego potrzebujesz, możesz wykonać prosty, ale może niebezpieczny test:

Wystarczy wprowadzić polecenie, które Cię interesuje, z argumentami, które już rozumiesz, i sprawdź, czy polecenie wstrzymuje się (nic się nie dzieje). Jeśli to wstrzymać, to jest rzeczywiście czeka na STDIN (można spróbować cati echozobaczyć różne). Wpisujesz ręcznie, Ctrl-Da polecenie idzie naprzód (pokazuje wyniki lub błędy) i zwraca. W takim przypadku takie polecenie wymaga STDIN (z podanymi argumentami).

To samo polecenie może nie wymagać STDIN w różnych sytuacjach (np. catCzeka na STDIN, ale cat file.txtnie).

Alex Huang
źródło