Değerleri borudan kabuk değişkenine okuma


205

Ben bok içine sokulmuş, ama şans yok stdin veri işlemek için bash çalışıyorum. Demek istediğim şu çalışmalardan hiçbiri:

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=

çıktı olmasını istiyorum test=hello world. Ben de "$test"işe yaramaz tırnak işaretleri "" koymaya çalıştım .


1
Örneğin .. echo "merhaba dünya" | testi oku; echo test = $ test benim için iyi çalıştı .. sonuç: test = merhaba dünya; bunu hangi ortamda yürütüyorsunuz? Ben bash 4.2 kullanıyorum ..
alex.pilon

Tek bir okumada birden fazla satır mı istiyorsunuz? Örneğiniz yalnızca bir satır gösterir, ancak sorun açıklaması net değildir.
Charles Duffy

2
@ alex.pilon, Bash 4.2.25 sürümünü çalıştırıyorum ve örneği benim için de çalışmıyor. Bu bir Bash çalışma zamanı seçeneği veya ortam değişkeni meselesi olabilir mi? Ben örnek Sh ile çalışmıyor, bu yüzden Bash Sh ile uyumlu olmaya çalışabilir miyim?
Hibou57

2
@ Hibou57 - Bunu 4.3.25 bash'da tekrar denedim ve artık çalışmıyor. Hafızam bu konuda bulanık ve işe yaraması için ne yapmış olabileceğimden emin değilim.
alex.pilon

2
@ Hibou57 @ alex.pilon bir borudaki son cmd bash4> = 4.2 değişkenlerini shopt -s lastpipe- tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT
imz - Ivan Zakharyaschev ile

Yanıtlar:


162

kullanım

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

Sen edebilirsiniz kandırmak readböyle bir borusundan kabul içine:

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

hatta şöyle bir işlev yazabilirsiniz:

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

Ama bir anlamı yok - değişken atamalarınız uzun sürebilir! Bir boru hattı, ortamın referans olarak değil, değere miras alındığı bir alt kabuk oluşturabilir. Bu yüzden readbir borudan gelen girdi ile uğraşmaz - tanımsızdır.

FYI, http://www.etalabs.net/sh_tricks.html bourne kabuklarının tuhaflıkları ve uyumsuzluklarıyla mücadele etmek için gerekli olan hammaddenin şık bir koleksiyonudur, sh.


Bunun yerine ödevi sonlandırabilirsiniz: `test =` `echo" merhaba dünya "| {testi okuyun; echo $ testi; } ``
Compholio

2
Bunu tekrar deneyelim (görünüşe göre bu biçimlendirmede backticks kaçmak eğlenceli):test=`echo "hello world" | { read test; echo $test; }`
Compholio

bu iki komutu gruplamak {}yerine neden kullandığınızı sorabilir miyim ()?
Jürgen Paul

4
Hile, readborudan girdi alma değil , aynı kabuğu çalıştıran değişkeni kullanmadadır read.
chepner

1
Got bash permission deniedbu çözümü kullanmaya çalışırken. Benim durum oldukça farklıdır, ama ben benim için çalıştı yerde bunun için bir cevap, (farklı bir örnek ama benzer kullanımını) bulamadık: pip install -U echo $(ls -t *.py | head -1). Durumda, birinin benzer bir sorunu vardır ve benim gibi bu cevaba tökezler.
ivan_bilan

111

çok fazla veri okumak ve her satırda ayrı ayrı çalışmak isterseniz, şöyle bir şey kullanabilirsiniz:

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

satırları birden çok kelimeye bölmek istiyorsanız, x yerine aşağıdaki gibi birden çok değişken kullanabilirsiniz:

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

alternatif olarak:

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

Ancak bu tür bir şeyle gerçekten akıllıca bir şey yapmaya başlar başlamaz, böyle bir şey deneyebileceğiniz perl gibi bir betik dili için daha iyi olacaksınız:

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

Perl ile oldukça dik bir öğrenme eğrisi var (ya da sanırım bu dillerden herhangi biri), ancak en basit komut dosyalarından başka bir şey yapmak istiyorsanız uzun vadede çok daha kolay bulacaksınız. Perl Yemek Kitabı'nı ve elbette Larry Wall ve ark.


8
"alternatif olarak" doğru yoldur. UUoC ve alt kabuk yok. Bakınız BashFAQ / 024 .
sonraki duyuruya kadar duraklatıldı.

43

readbir borudan okumaz (veya boru bir alt kabuk oluşturduğu için sonuç kaybedilir). Bununla birlikte, Bash'te burada bir dize kullanabilirsiniz:

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

Ama hakkında bilgi için @ chepner'ın cevabına bakınız lastpipe.


Güzel ve basit bir astar, anlaşılması kolay. Bu cevap için daha fazla oy gerekiyor.
David Parks

42

Bu başka bir seçenek

$ read test < <(echo hello world)

$ echo $test
hello world

24
Önemli avantajı <(..)edemediği $(..)olmasıdır <(..)döner çalıştırır, komut kullanılabilir hale getirir yakında kadar arayana her satırı. $(..)ancak, komut, arayan tarafından herhangi bir çıktı alınmadan önce tüm çıktısının tamamlanmasını ve oluşturulmasını bekler.
Derek Mahar

37

Bash konusunda uzman değilim, ama bunun neden önerilmediğini merak ediyorum:

stdin=$(cat)

echo "$stdin"

Benim için işe yaradığını gösteren tek astarlı bir kanıt:

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

4
Muhtemelen "read" bir bash komutudur ve cat, bir alt işlemde başlatılacak ayrı bir ikili dosya olduğundan, daha az verimlidir.
dj_segfault

10
bazen basitlik ve netlik koz verimliliği :)
Rondo

5
kesinlikle en basit cevap
drwatsoncode 6:15

Ben bu konuda koştu sorun bir boru kullanılmazsa, komut dosyası askıda olmasıdır.
Dale Anderson

@DaleA Elbette. Hiçbir şey beslenmezse standart girdiden okumaya çalışan herhangi bir program "askıda kalır".
djanowski

27

bash4.2 lastpipe, alt kabuk yerine geçerli kabuktaki bir boru hattındaki son komutu yürüterek kodunuzun yazıldığı gibi çalışmasını sağlayan seçeneği sunar.

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

2
Ah! çok iyi, bu. etkileşimli bir kabukta test ediyorsanız, ayrıca: "set + m" (bir .sh komut dosyasında gerekli değildir)
XXL

14

Bir kabuk komutundan bir bash değişkenine örtülü bir borunun sözdizimi:

var=$(command)

veya

var=`command`

Örneklerinizde, herhangi bir girdi beklemeyen bir atama deyimine veri aktarıyorsunuz.


4
Çünkü $ () kolayca yuvalanabilir. JAVA_DIR = $ (dirname $ (readlink -f $ (hangi java))) düşünün ve `ile deneyin. Üç kez kaçman gerekecek!
albfan

8

İlk girişim oldukça yakındı. Bu varyasyonun çalışması gerekir:

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

ve çıktı:

test = merhaba dünya

Test için atamayı ve yankıyı kapsayacak şekilde borudan sonra parantezlere ihtiyacınız vardır.

Diş telleri olmadan, test ataması (borudan sonra) bir kabukta ve "test = $ test" yankı, bu atama hakkında bilgi sahibi olmayan ayrı bir kabukta. Bu yüzden çıktıda "test = merhaba dünya" yerine "test =" alıyordunuz.


8

Benim gözümde, bash'ta stdin'den okumanın en iyi yolu, giriş bitmeden önce satırlar üzerinde çalışmanıza izin veren aşağıdakilerdir:

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

1
Bunu bulmadan neredeyse delirmiştim. Paylaştığınız için çok teşekkürler!
Samy Dindane

6

Çünkü bunun için düşüyorum, bir not bırakmak istiyorum. POSIX uyumlu olmak için eski bir sh komut dosyasını yeniden yazmak zorunda çünkü bu iş parçacığı bulundu. Bu temel olarak POSIX tarafından getirilen boru / alt kabuk sorununu aşağıdaki gibi kod yeniden yazarak atlatmak anlamına gelir:

some_command | read a b c

içine:

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

Ve şöyle kodlayın:

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

içine:

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

Ancak ikincisi boş girdide aynı şekilde davranmaz. Eski gösterimle while döngüsü boş girdiye girilmez, POSIX gösterimlerinde girilir! Bence bu, EOF'dan önceki yeni çizgiden kaynaklanamıyor. Eski gösterime daha çok benzeyen POSIX kodu şöyle görünür:

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

Çoğu durumda bu yeterince iyi olmalıdır. Ancak maalesef, some_command boş bir satır yazdırırsa, bu hala eski notasyon gibi davranmaz. Eski notasyonda while gövdesi idam edilir ve POSIX notasyonunda bedenin önünde kırılırız.

Bunu düzeltmek için bir yaklaşım şöyle görünebilir:

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

Hem PIPE'den hem de komut satırı bağımsız değişkenlerinden veri okuyabilen akıllı bir komut dosyası:

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

Çıktı:

$ bash test arg1 arg2
ARGS=arg1 arg2

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

Açıklama: Bir komut dosyası kanal yoluyla herhangi bir veri aldığında, stdin / proc / self / fd / 0 bir boruya bir sembolik bağlantı olacaktır.

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

Değilse, mevcut terminali gösterecektir:

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

Bash [[ -pseçeneği bir boru olup olmadığını kontrol edebilir.

cat -den okur stdin.

Kullandığımız takdirde cat -hiçbir varken stdin, biz içine koymak yüzden, sonsuza bekler ifdurumda.


Ayrıca /dev/stdinbu linki kullanabilirsiniz/proc/self/fd/0
Elie G.

3

Bir ödevi içeren bir ifadeye bir şey eklemek, böyle davranmaz.

Bunun yerine şunu deneyin:

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

2

Aşağıdaki kod:

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

aynı zamanda çalışacak, ancak borudan sonra yeni bir alt kabuk açacak.

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

alışkanlık.


Chepnars'ın yöntemini kullanmak için iş kontrolünü devre dışı bırakmak zorunda kaldım (bu komutu terminalden çalıştırıyordum):

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

Bash Manual diyor :

lastpipe

Ayarlanmışsa ve iş denetimi etkin değilse , kabuk geçerli kabuk ortamında arka planda yürütülmeyen bir boru hattının son komutunu çalıştırır.

Not: iş kontrolü etkileşimli olmayan bir kabukta varsayılan olarak kapalıdır ve bu nedenle set +mbir komut dosyasının içine ihtiyacınız yoktur .


1

Sanırım stdin'den girdi alabilen bir kabuk senaryosu yazmaya çalışıyordun. ancak bunu satır içinde yapmaya çalışırken, bu test = değişkenini oluşturmaya çalışırken kayboldunuz. Satır içi yapmak pek mantıklı değil ve bu yüzden beklediğiniz gibi çalışmıyor.

Azaltmaya çalışıyordum

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

çeşitli girdilerden belirli bir satır almak için. bu yüzden yazabilirim ...

cat program_file.c | line 34

bu yüzden stdin'den okuyabilen küçük bir kabuk programına ihtiyacım var. senin yaptığın gibi.

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 ~ $ 

İşte böyle.


-1

Buna ne dersin:

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

Temelde aşağıdaki cevabımdan bir dupe.
djanowski

Belki, benim orijinal soruya gönderilen koda biraz daha temiz ve daha yakın olduğunu iddia ediyorum.
Bingles
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.