Wczytaj wartości do zmiennej powłoki z potoku


205

Próbuję uzyskać bash do przetwarzania danych ze standardowego wejścia, które jest przesyłane do, ale bez powodzenia. Mam na myśli to, że żadna z następujących prac:

echo "hello world" | test=($(< /dev/stdin)); echo test=$test
test=

echo "hello world" | read test; echo test=$test
test=

echo "hello world" | test=`cat`; echo test=$test
test=

gdzie chcę, aby wynik był test=hello world. Próbowałem wstawiać cytaty "$test", ale to też nie działa.


1
Twój przykład .. echo „witaj świecie” | test odczytu; test echa = $ test działał dobrze dla mnie .. wynik: test = witaj świecie; w jakim środowisku to działa? Używam bash 4.2 ..
alex.pilon

Czy chcesz wielu wierszy w jednym odczycie? Twój przykład pokazuje tylko jedną linię, ale opis problemu jest niejasny.
Charles Duffy

2
@ alex.pilon, używam wersji Bash 4.2.25, a jego przykład też nie działa dla mnie. Może to zależy od opcji środowiska wykonawczego Bash lub zmiennej środowiskowej? Mam przykład, że nie działa również z Sh, więc może Bash może próbować być kompatybilny z Sh?
Hibou57

2
@ Hibou57 - Próbowałem tego ponownie w bash 4.3.25 i to już nie działa. Moja pamięć jest niewyraźna i nie jestem pewien, co mogłem zrobić, aby to zadziałało.
alex.pilon

2
@ Hibou57 @ alex.pilon ostatni cmd w potoku powinien wpływać na zmienne w bash4> = 4.2 z shopt -s lastpipe- tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT
imz - Ivan Zakharyaschev

Odpowiedzi:


162

Posługiwać się

IFS= read var << EOF
$(foo)
EOF

Państwo może skłonić readdo przyjęcia z rury tak:

echo "hello world" | { read test; echo test=$test; }

lub nawet napisz taką funkcję:

read_from_pipe() { read "$@" <&0; }

Ale nie ma sensu - przypisania zmiennych mogą nie trwać! Potok może odrodzić podpowłokę, w której środowisko jest dziedziczone przez wartość, a nie przez referencję. To dlatego readnie przeszkadza wejście z potoku - jest niezdefiniowane.

FYI, http://www.etalabs.net/sh_tricks.html to sprytna kolekcja cruft niezbędna do zwalczania osobliwości i niekompatybilności muszli Bourne'a, sh.


Możesz sprawić, by zadanie trwało dłużej, wykonując to w zamian: `test =` `echo" hello world "| {czytaj test; test echa $; } ``
Compholio

2
Spróbujmy jeszcze raz (najwyraźniej ucieczka przed backtickami w tym znaczniku jest fajna):test=`echo "hello world" | { read test; echo $test; }`
Compholio

czy mogę zapytać, dlaczego użyłeś {}zamiast ()grupować te dwa polecenia?
Jürgen Paul,

4
Sztuczka nie polega na readpobieraniu danych z potoku, ale na używaniu zmiennej w tej samej powłoce, która wykonuje polecenie read.
chepner

1
Dostałem, bash permission deniedgdy próbujesz użyć tego rozwiązania. Moja sprawa jest zupełnie inna, ale nie mogłem znaleźć odpowiedź na to gdzie, co pracował dla mnie (inny przykład, ale podobną użycia) pip install -U echo $(ls -t *.py | head -1). Na wypadek, gdyby ktoś miał podobny problem i natknął się na tę odpowiedź, tak jak ja.
ivan_bilan

111

jeśli chcesz czytać wiele danych i pracować nad każdą linią osobno, możesz użyć czegoś takiego:

cat myFile | while read x ; do echo $x ; done

jeśli chcesz podzielić wiersze na wiele słów, możesz użyć wielu zmiennych zamiast x:

cat myFile | while read x y ; do echo $y $x ; done

alternatywnie:

while read x y ; do echo $y $x ; done < myFile

Ale gdy tylko zaczniesz robić coś naprawdę sprytnego z tego rodzaju rzeczami, lepiej wybrać język skryptowy, taki jak Perl, w którym możesz spróbować czegoś takiego:

perl -ane 'print "$F[0]\n"' < myFile

W Perlu jest dość stroma krzywa uczenia się (lub, jak sądzę, któregoś z tych języków), ale na dłuższą metę będzie ci o wiele łatwiej, jeśli chcesz robić tylko najprostsze skrypty. Poleciłbym Perl Cookbook i, oczywiście, Perl Programming Language autorstwa Larry'ego Walla i in.


8
„alternatywnie” to właściwy sposób. Bez UUoC i bez podpowłoki. Zobacz BashFAQ / 024 .
Wstrzymano do odwołania.

43

readnie będzie czytać z potoku (być może wynik zostanie utracony, ponieważ potok tworzy podpowłokę). Możesz jednak użyć ciągu tutaj w Bash:

$ read a b c <<< $(echo 1 2 3)
$ echo $a $b $c
1 2 3

Ale zobacz odpowiedź @ chepnera, aby uzyskać informacje na temat lastpipe.


Ładny i prosty jednowarstwowy, łatwy do zrozumienia. Ta odpowiedź wymaga więcej głosów pozytywnych.
David Parks

42

To kolejna opcja

$ read test < <(echo hello world)

$ echo $test
hello world

24
Istotną zaletą <(..)jest $(..)to, że <(..)zwraca każdą linię dzwoniącemu, gdy tylko polecenie, które wykonuje, udostępnia ją. $(..), jednak czeka na zakończenie komendy i wygenerowanie wszystkich danych wyjściowych, zanim udostępni dane wywołujące.
Derek Mahar

37

Nie jestem ekspertem w Bash, ale zastanawiam się, dlaczego nie zaproponowano tego:

stdin=$(cat)

echo "$stdin"

Jednoliniowy dowód, że to działa dla mnie:

$ fortune | eval 'stdin=$(cat); echo "$stdin"'

4
Prawdopodobnie dlatego, że „read” jest poleceniem bash, a cat to osobny plik binarny, który zostanie uruchomiony w podprocesie, więc jest mniej wydajny.
dj_segfault

10
czasem prostota i klarowność wydajność atutowa :)
Rondo

5
zdecydowanie najprostsza odpowiedź
drwatsoncode

Problem, z którym się zetknąłem, polega na tym, że jeśli rura nie jest używana, skrypt zawiesza się.
Dale Anderson

@DaleA Cóż, oczywiście. Każdy program, który próbuje odczytać ze standardowego wejścia, „zawiesi się”, jeśli nic do niego nie zostanie podane.
djanowski

27

bash4.2 wprowadza lastpipeopcję, która pozwala twojemu kodowi działać tak, jak napisano, wykonując ostatnie polecenie w potoku w bieżącej powłoce, a nie w podpowłoce.

shopt -s lastpipe
echo "hello world" | read test; echo test=$test

2
ah! tak dobrze, to. jeśli testujesz w interaktywnej powłoce, również: „set + m” (niewymagany w skrypcie .sh)
XXL

14

Składnia niejawnego potoku z polecenia powłoki do zmiennej bash to

var=$(command)

lub

var=`command`

W swoich przykładach przesyłasz dane do instrukcji przypisania, która nie oczekuje żadnych danych wejściowych.


4
Ponieważ $ () można łatwo zagnieżdżać. Pomyśl w JAVA_DIR = $ (nazwa katalogu $ (readlink -f $ (która java))) i spróbuj z `. Musisz uciec trzy razy!
albfan

8

Pierwsza próba była dość bliska. Ta odmiana powinna działać:

echo "hello world" | { test=$(< /dev/stdin); echo "test=$test"; };

a wynikiem jest:

test = witaj świecie

Potrzebne są nawiasy klamrowe za rurą, aby zamknąć zadanie do przetestowania i echa.

Bez nawiasów przypisanie do testowania (po potoku) znajduje się w jednej powłoce, a echo „test = $ test” znajduje się w osobnej powłoce, która nie wie o tym przypisaniu. Dlatego otrzymujesz „test =” w danych wyjściowych zamiast „test = witaj świecie”.


8

Moim zdaniem najlepszy sposób na czytanie ze standardowego wejścia w bash jest następujący, który pozwala również pracować na liniach przed zakończeniem wprowadzania:

while read LINE; do
    echo $LINE
done < /dev/stdin

1
Prawie oszalałem, zanim to znalazłem. Wielkie dzięki za udostępnienie!
Samy Dindane,

6

Ponieważ mi się to podoba, chciałbym upuścić notatkę. Znalazłem ten wątek, ponieważ muszę przepisać stary skrypt sh, aby był zgodny z POSIX. Zasadniczo oznacza to obejście problemu potoku / podpowłoki wprowadzonego przez POSIX poprzez przepisanie kodu w następujący sposób:

some_command | read a b c

w:

read a b c << EOF
$(some_command)
EOF

I kod w ten sposób:

some_command |
while read a b c; do
    # something
done

w:

while read a b c; do
    # something
done << EOF
$(some_command)
EOF

Ale to ostatnie nie zachowuje się tak samo przy pustym wejściu. W starej notacji pętla while nie jest wprowadzana przy pustym wejściu, ale w notacji POSIX jest! Myślę, że wynika to z nowej linii przed EOF, której nie można pominąć. Kod POSIX, który zachowuje się bardziej jak stara notacja, wygląda następująco:

while read a b c; do
    case $a in ("") break; esac
    # something
done << EOF
$(some_command)
EOF

W większości przypadków powinno to być wystarczająco dobre. Ale niestety to nadal nie działa dokładnie tak, jak stara notacja, jeśli some_command wypisuje pustą linię. W starej notacji ciało while jest wykonywane, aw notacji POSIX łamiemy się przed ciałem.

Podejście do rozwiązania tego problemu może wyglądać następująco:

while read a b c; do
    case $a in ("something_guaranteed_not_to_be_printed_by_some_command") break; esac
    # something
done << EOF
$(some_command)
echo "something_guaranteed_not_to_be_printed_by_some_command"
EOF

5

Inteligentny skrypt, który może zarówno odczytywać dane z PIPE, jak i argumenty wiersza poleceń:

#!/bin/bash
if [[ -p /proc/self/fd/0 ]]
    then
    PIPE=$(cat -)
    echo "PIPE=$PIPE"
fi
echo "ARGS=$@"

Wynik:

$ bash test arg1 arg2
ARGS=arg1 arg2

$ echo pipe_data1 | bash test arg1 arg2
PIPE=pipe_data1
ARGS=arg1 arg2

Objaśnienie: Kiedy skrypt odbiera dane przez potok, wtedy stdin / proc / self / fd / 0 będzie dowiązaniem symbolicznym do potoku.

/proc/self/fd/0 -> pipe:[155938]

Jeśli nie, wskaże bieżący terminal:

/proc/self/fd/0 -> /dev/pts/5

Opcja bash [[ -pmoże sprawdzić, czy jest to potok, czy nie.

cat -czyta z stdin.

Jeśli użyjemy, cat -gdy nie ma stdin, będzie czekać wiecznie, dlatego umieszczamy go w ifwarunku.


Możesz także użyć /dev/stdinlinku do/proc/self/fd/0
Elie G.

3

Przeniesienie czegoś do wyrażenia obejmującego zadanie nie zachowuje się w ten sposób.

Zamiast tego spróbuj:

test=$(echo "hello world"); echo test=$test

2

Poniższy kod:

echo "hello world" | ( test=($(< /dev/stdin)); echo test=$test )

też będzie działać, ale otworzy kolejną nową podpowłokę za potokiem, gdzie

echo "hello world" | { test=($(< /dev/stdin)); echo test=$test; }

przyzwyczajenie.


Musiałem wyłączyć kontrolę zadań, aby skorzystać z metody Chepnars (uruchamiałem to polecenie z terminala):

set +m;shopt -s lastpipe
echo "hello world" | read test; echo test=$test
echo "hello world" | test="$(</dev/stdin)"; echo test=$test

Bash Manual mówi :

ostatnia rura

Jeśli jest ustawiony, a kontrola zadań nie jest aktywna , powłoka uruchamia ostatnie polecenie potoku, które nie zostało wykonane w tle w bieżącym środowisku powłoki.

Uwaga: kontrola zadań jest domyślnie wyłączona w nieinteraktywnej powłoce, dlatego nie potrzebujesz set +mwewnętrznej strony skryptu.


1

Myślę, że próbowałeś napisać skrypt powłoki, który mógłby pobierać dane wejściowe ze standardowego wejścia. ale kiedy próbujesz to zrobić in-line, zgubiłeś się, próbując utworzyć zmienną test =. Myślę, że nie ma większego sensu robienie tego w linii i dlatego nie działa tak, jak się spodziewasz.

Próbowałem zmniejszyć

$( ... | head -n $X | tail -n 1 )

aby uzyskać konkretny wiersz z różnych danych wejściowych. żebym mógł napisać ...

cat program_file.c | line 34

więc potrzebuję małego programu powłoki, który może czytać ze standardowego wejścia. tak jak ty.

22:14 ~ $ cat ~/bin/line 
#!/bin/sh

if [ $# -ne 1 ]; then echo enter a line number to display; exit; fi
cat | head -n $1 | tail -n 1
22:16 ~ $ 

proszę bardzo.


-1

Co powiesz na to:

echo "hello world" | echo test=$(cat)

Zasadniczo dupe z mojej odpowiedzi poniżej.
djanowski

Być może twierdzę, że mój jest nieco czystszy i bliższy kodowi zamieszczonemu w pierwotnym pytaniu.
bingles
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.