Przechwytywanie danych wyjściowych z wielu wierszy w zmiennej Bash

580

Mam skrypt „myscript”, który wyświetla następujące informacje:

abc
def
ghi

w innym skrypcie wywołuję:

declare RESULT=$(./myscript)

i $RESULTotrzymuje wartość

abc def ghi

Czy istnieje sposób na zapisanie wyniku w znaku nowej linii lub znaku „\ n”, aby można go było wyświetlić za pomocą „ echo -e”?

Parker
źródło
1
to mnie zaskakuje. nie masz $ (cat ./myscipt)? w przeciwnym razie spodziewałbym się, że spróbuje wykonać polecenia abc, def i ghi
Johannes Schaub - litb
@litb: tak, tak sądzę; możesz także użyć $ (<./ myscript), co pozwala uniknąć wykonania polecenia.
Jonathan Leffler,
2
(Uwaga: dwa powyższe komentarze odnoszą się do wersji pytania, które się rozpoczęło. Mam skrypt „myscript”, który zawiera następujące elementy , które doprowadziły do ​​pytań. Bieżąca wersja pytania ( mam skrypt ” myscript ”, który wypisuje następujące ), powoduje, że komentarze stają się zbędne. Jednak poprawka pochodzi z 11.11.2011, długo po tym, jak dwa komentarze zostały zgłoszone.
Jonathan Leffler

Odpowiedzi:

1077

W rzeczywistości RESULT zawiera to, czego chcesz - aby zademonstrować:

echo "$RESULT"

To, co pokazujesz, to:

echo $RESULT

Jak zauważono w komentarzach, różnica polega na tym, że (1) podwójnie cytowana wersja zmiennej ( echo "$RESULT") zachowuje wewnętrzne odstępy wartości dokładnie tak, jak jest ona reprezentowana w zmiennej - nowe linie, tabulatory, wiele pustych miejsc i wszystko - podczas gdy (2 ) wersja bez cudzysłowu ( echo $RESULT) zastępuje każdą sekwencję jednego lub więcej spacji, tabulatorów i znaków nowej linii pojedynczą spacją. Tak więc (1) zachowuje kształt zmiennej wejściowej, podczas gdy (2) tworzy potencjalnie bardzo długi pojedynczy wiersz wyjściowy ze „słowami” oddzielonymi pojedynczymi spacjami (gdzie „słowo” jest sekwencją znaków innych niż spacje; potrzeba nie może zawierać żadnych znaków alfanumerycznych w żadnym ze słów).

Jonathan Leffler
źródło
69
@troelskn: różnica polega na tym, że (1) podwójnie cytowana wersja zmiennej zachowuje wewnętrzne odstępy wartości dokładnie tak, jak jest ona reprezentowana w zmiennej, znakach nowej linii, tabulatorach, wielu odstępach i wszystkim, podczas gdy (2) wersja niecytowana zastępuje każda sekwencja jednego lub więcej spacji, tabulatorów i znaków nowej linii z pojedynczym odstępem. Tak więc (1) zachowuje kształt zmiennej wejściowej, podczas gdy (2) tworzy potencjalnie bardzo długi pojedynczy wiersz wyjściowy ze „słowami” oddzielonymi pojedynczymi spacjami (gdzie „słowo” jest sekwencją znaków innych niż spacje; potrzeba nie może zawierać żadnych znaków alfanumerycznych w żadnym ze słów).
Jonathan Leffler,
23
Aby odpowiedź była łatwiejsza do zrozumienia: odpowiedź mówi, że echo „$ RESULT” zachowuje nową linię, podczas gdy echo $ RESULT nie.
Yu Shen,
W niektórych sytuacjach nie zachowuje to nowych linii i wiodących spacji.
CommaToast,
3
@CommaToast: Zamierzasz rozwinąć tę kwestię? Końcowe nowe linie są tracone; nie ma łatwego sposobu na obejście tego. Wiodące puste pola - nie jestem świadomy żadnych okoliczności, w których zostałyby zgubione.
Jonathan Leffler,
90

Inną pułapką jest to, że zastępowanie poleceń - $()- usuwa końcowe znaki nowej linii. Prawdopodobnie nie zawsze jest to ważne, ale jeśli naprawdę chcesz zachować dokładnie to , co zostało wydrukowane, musisz użyć innej linii i cytowania:

RESULTX="$(./myscript; echo x)"
RESULT="${RESULTX%x}"

Jest to szczególnie ważne, jeśli chcesz obsłużyć wszystkie możliwe nazwy plików (aby uniknąć niezdefiniowanego zachowania, takiego jak operowanie na niewłaściwym pliku).

l0b0
źródło
4
Musiałem przez chwilę pracować ze zepsutą powłoką, która nie usunęła ostatniego nowego wiersza z podstawienia polecenia (nie jest to podstawienie procesu ) i prawie wszystko zepsuło. Na przykład, jeśli tak pwd=`pwd`; ls $pwd/$file, to przed linią masz nowy wiersz /, a umieszczenie nazwy w podwójnych cudzysłowach nie pomogło. Zostało to naprawione szybko. To było w przedziale czasowym 1983-5 na ICL Perq PNX; powłoka nie miała $PWDwbudowanej zmiennej.
Jonathan Leffler,
19

Jeśli interesują Cię określone wiersze, użyj tablicy wyników:

declare RESULT=($(./myscript))  # (..) = array
echo "First line: ${RESULT[0]}"
echo "Second line: ${RESULT[1]}"
echo "N-th line: ${RESULT[N]}"
użytkownik2574210
źródło
3
Jeśli w wierszach są spacje, policzone zostaną pola (zawartość między spacjami), a nie linie.
Liam
2
Można by użyć readarrayi proces zastępowania zamiast podstawienia polecenia: readarray -t RESULT < <(./myscript>.
chepner,
15

Oprócz odpowiedzi udzielonej przez @ l0b0 miałem właśnie sytuację, w której musiałem zarówno zachować wszystkie końcowe znaki nowej linii przez skrypt i sprawdzić kod powrotu skryptu. Problem z odpowiedzią l0b0 polega na tym, że „echo x” resetuje $? powrót do zera ... więc udało mi się wymyślić to bardzo przebiegłe rozwiązanie:

RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"
Lurchman
źródło
To cudowne, faktycznie miałem przypadek użycia, w którym chciałbym mieć die ()funkcję, która akceptuje dowolny kod wyjścia i opcjonalnie wiadomość, i chciałem użyć innej usage ()funkcji do dostarczenia wiadomości, ale nowe linie były ciągle miażdżone, mam nadzieję, że to pozwala mi obejść to.
dragon788,
1

Po wypróbowaniu większości rozwiązań tutaj najłatwiejszą rzeczą, jaką znalazłem, była oczywistość - użycie pliku tymczasowego. Nie jestem pewien, co chcesz zrobić z wyjściem wieloliniowym, ale możesz sobie z tym poradzić, czytając. Jedyną rzeczą, której tak naprawdę nie można zrobić, to łatwo umieścić to wszystko w tej samej zmiennej, ale dla większości praktycznych celów jest to o wiele łatwiejsze.

./myscript.sh > /tmp/foo
while read line ; do 
    echo 'whatever you want to do with $line'
done < /tmp/foo

Szybki hack, aby wykonać żądaną akcję:

result=""
./myscript.sh > /tmp/foo
while read line ; do
  result="$result$line\n"
done < /tmp/foo
echo -e $result

Uwaga: dodaje to dodatkową linię. Jeśli nad tym popracujesz, możesz go zakodować, jestem po prostu zbyt leniwy.


EDYCJA: Chociaż ten przypadek działa idealnie dobrze, osoby czytające to powinny mieć świadomość, że możesz łatwo zgnieść swoje standardowe wejście w pętli while, dając w ten sposób skrypt, który uruchomi jedną linię, wyczyści standardowe wejście i zakończy działanie. Jak ssh zrobi to, co myślę? Właśnie to widziałem, inne przykłady kodu tutaj: /unix/24260/reading-lines-from-a-file-with-bash-for-vs-while

Jeszcze raz! Tym razem z innym uchwytem pliku (stdin, stdout, stderr to 0-2, więc możemy używać & 3 lub wyższej w bash).

result=""
./test>/tmp/foo
while read line  <&3; do
    result="$result$line\n"
done 3</tmp/foo
echo -e $result

możesz także użyć mktemp, ale to tylko szybki przykład kodu. Użycie mktemp wygląda następująco:

filenamevar=`mktemp /tmp/tempXXXXXX`
./test > $filenamevar

Następnie użyj $ filenamevar, tak jak rzeczywista nazwa pliku. Prawdopodobnie nie trzeba tu wyjaśniać, ale ktoś narzekał w komentarzach.

użytkownik1279741
źródło
Próbowałem też innych rozwiązań, z twoją pierwszą sugestią, że w końcu udało mi się
uruchomić
1
Downvote: Jest to zbyt skomplikowane i nie pozwala uniknąć wielu typowych bashpułapek .
tripleee
Ktoś powiedział mi kiedyś o dziwnym problemie z uchwytem pliku stdin, a ja byłem jak wow. Dodaj mi coś naprawdę szybko.
user1279741
W Bash możesz użyć readrozszerzenia polecenia -u 3do odczytu z deskryptora pliku 3.
Jonathan Leffler,
Dlaczego nie, póki ... nie ... zrobione <<(. Myscript.sh)
jtgd
0

Co powiesz na to, odczytuje każdą linię do zmiennej i można z niej później korzystać! powiedzmy, że wyjście myscript jest przekierowywane do pliku o nazwie myscript_output

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'
Rahul Reddy
źródło
4
Cóż, to nie jest bash, to awk.
vadipp