Bash'deki bir dosyadan veya STDIN'den nasıl okunur?


244

Aşağıdaki Perl betiği ( my.pl), komut satırı argümanlarındaki dosyadan veya STDIN'den okuyabilir:

while (<>) {
   print($_);
}

perl my.plSTDIN'den perl my.pl a.txtokuyacak, okuyacak a.txt. Bu çok uygun.

Bash'te bir eşdeğeri olduğunu merak ediyor musunuz?

Yanıtlar:


409

Komut dosyası, $1standart girişten ilk parametre olarak bir dosya adıyla çağrılırsa, aşağıdaki çözüm bir dosyadan okur .

while read line
do
  echo "$line"
done < "${1:-/dev/stdin}"

İkamesi ${1:-...}alır $1aksi kendi sürecin standart girdi dosya adı kullanılır tanımlı.


1
Güzel, işe yarıyor. Başka bir soru, bunun için neden bir teklif eklediğinizdir? "$ {1: - / proc / $ {$} / fd / 0}"
Dagang

15
Komut satırında sağladığınız dosya adında boşluklar olabilir.
Fritz G. Mehner

3
Kullanarak arasında herhangi bir fark var mı /proc/$$/fd/0ve /dev/stdin? İkincisinin daha yaygın ve daha basit göründüğünü fark ettim.
knowah

19
Komutunuza eklemek -rdaha readiyidir, böylece yanlışlıkla \ karakter yemeyin ; while IFS= read -r lineöncü ve sondaki boşlukları korumak için kullanın .
mklement0

1
@NeDark: Bu meraklı; Sadece bu platformda çalıştığını doğruladım, kullanırken bile /bin/sh- bashveya dışında bir kabuk kullanıyor shmusunuz?
mklement0

119

Belki de en basit çözüm, stdin'i bir birleştirme yönlendirme operatörü ile yönlendirmektir:

#!/bin/bash
less <&0

Stdin, dosya tanımlayıcı sıfırdır. Yukarıdakiler, bash betiğinize aktarılan girişi daha az stdin'e gönderir.

Dosya tanımlayıcı yeniden yönlendirmesi hakkında daha fazla bilgi edinin .


1
Keşke sana verecek daha fazla oyum olsaydı, bunu yıllardır arıyordum.
Marcus Downing

13
<&0Bu durumda kullanmanın bir yararı yoktur - örneğiniz, onunla veya onsuz aynı şekilde çalışacaktır - görünüşe göre, bir bash komut dosyasından çağırdığınız araçlar, varsayılan olarak komut dosyasının kendisiyle aynı stdin'i görür (komut dosyası önce tüketmedikçe).
mklement0

@ mkelement0 Bir araç giriş arabelleğinin yarısını okursa, çağırdığım bir sonraki araç geri kalanını alacak mı?
Asad Saeeduddin

Bunu yaptığımda "Eksik dosya adı (" yardım için daha az "yardım") "Ubuntu 16.04
OmarOthman

5
bu cevabın "veya dosyadan" kısmı nerede?
Sebastian

84

İşte en basit yol:

#!/bin/sh
cat -

Kullanımı:

$ echo test | sh my_script.sh
test

Değişkene stdin atamak için şunu kullanabilirsiniz: STDIN=$(cat -)ya da sadece STDIN=$(cat)operatör gerekli olmadığı için ( @ mklement0 yorumuna göre ).


Her satırı standart girdiden ayrıştırmak için aşağıdaki komut dosyasını deneyin:

#!/bin/bash
while IFS= read -r line; do
  printf '%s\n' "$line"
done

Dosyadan veya stdin'den okumak için (bağımsız değişken yoksa), aşağıdakine genişletebilirsiniz:

#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")

Notlar:

- read -r- Ters eğik çizgi karakterine herhangi bir özel işlem yapmayın. Her ters eğik çizgiyi giriş satırının bir parçası olarak düşünün.

- Ayar yapılmadan IFS, varsayılan olarak çizgilerin sırası Spaceve Tabbaşında ve sonunda diziler yoksayılır (kesilir).

- Kullanım printfyerine echoçizgi bir tek oluştuğunda boş satırları bastırılmasının önüne geçmek için -e, -nya da -E. Ancak env POSIXLY_CORRECT=1 echo "$line", harici GNU'nuzu echodestekleyen onu kullanan bir geçici çözüm vardır . Bakınız: "-e" yi nasıl yankılarım?

Bakınız: Hiçbir argüman aktarılmadığında stdin nasıl okunur? stackoverflow SE şirketinde


Sen basitleştirebiliris [ "$1" ] && FILE=$1 || FILE="-"için FILE=${1:--}. (Quibble: ortam değişkenleriyle ad çakışmalarını önlemek için tüm büyük harfli kabuk değişkenlerinden kaçınmak daha iyidir .)
mklement0

Zevkle; Aslında ${1:--} olan tüm kabukları POSIX'deki benzeri içinde çalışması gerekir böylece, POSIX uyumlu. Tüm bu kabuklarda işe yaramayacak olan şey işlem ikamesi ( <(...)); örneğin bash, ksh, zsh, ancak tire ile çalışmaz. Ayrıca, daha iyi eklemek -riçin için readyanlışlıkla yemez böylece, komuta \ karakter; prepend IFS= ön ve boşluk arka korumak için.
mklement0

4
Aslında kodunuzu hala nedeniyle sonları echobir çizgi oluşuyorsa: -e, -nya -Eda gösterilmeyecektir. Bunu düzeltmek için, kullanmak gerekir printf: printf '%s\n' "$line". Önceki düzenlememe dahil etmedim… Bu hatayı düzelttiğimde yaptığım düzenlemeler çok sık geri alındı :(.
gniourf_gniourf

1
Hayır, başarısız olmaz. Ve --ilk argüman ise işe yaramaz'%s\n'
gniourf_gniourf

1
Cevabın bana göre para cezası (Yani artık farkında olduğum hiçbir hata veya istenmeyen özellik yok) —Perl'in yaptığı gibi birden fazla argümanı ele almasa da. Aslında, birden argümanları ele almak istiyorum, kullanmak istiyorum çünkü iyi olurdu Jonathan Leffler mükemmel aslında cevabı size ait yazma bitireceğiz IFS=ile readve printfyerine echo. :).
gniourf_gniourf

19

Bence bu basit bir yol:

$ cat reader.sh
#!/bin/bash
while read line; do
  echo "reading: ${line}"
done < /dev/stdin

-

$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
  echo "line ${i}"
done

-

$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5

4
Bu, posterin stdin veya bir dosya argümanından okuma gereksinimine uymuyor, bu sadece stdin'den okuyor.
nash

2
Kenara Nash'in geçerli itirazı @ Leaving: readstdin'i okur varsayılan olarak öylesine var, gerek için < /dev/stdin.
mklement0

13

echoÇözelti her yeni satır ekler IFSgiriş akışı keser. @ fgm'nin cevabı biraz değiştirilebilir:

cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"

Ne demek istediğini açıklayabilir misin? "Ecs girdi akışını kırarken echo çözümü yeni satırlar ekler"? Eğer atıfta bulundu readdavranış s': süre read yok potansiyel karakter göre birden fazla jeton bölündü. içerdiği $IFSbu sadece bir döner, tek Yalnızca belirtirseniz belirteci tek değişken adı (ama kapaklarının ve lider ve varsayılan olarak sonlarındaki boşluk).
mklement0

@ mklement0 Ben davranışına size% 100 katılıyorum readve $IFS- echokendisi olmadan yeni satır ekler -nbayrak. "Echo yardımcı programı, belirtilen işlenenleri tekli boş (` `) karakterlerle ayırarak ardından yeni satır (` \ n ') karakteri ile standart çıktıya yazar. "
David Souther

Anladım. Ancak, Perl döngü taklit gerek sondaki \ntarafından eklenen echo: Perl en $_ içerir biten çizgi \nbash en olurken, hattan okumak readdeğildir. (@Gniourf_gniourf başka işaret ettiği gibi, ancak, daha sağlam bir yaklaşım kullanım için printf '%s\n'yerine echo).
mklement0

8

Sorudaki Perl döngüsü , komut satırındaki tüm dosya adı bağımsız değişkenlerinden veya dosya belirtilmezse standart girdiden okunur . Gördüğüm tüm yanıtlar, herhangi bir dosya belirtilmemişse tek bir dosyayı veya standart girişi işliyor gibi görünüyor.

Genellikle UUOC (Yararsız Kullanım cat) olarak doğru bir şekilde atılsa dacat , iş için en iyi araç olduğu zamanlar vardır ve bunun bunlardan biri olduğu tartışmalıdır:

cat "$@" |
while read -r line
do
    echo "$line"
done

Bunun tek dezavantajı, alt kabukta çalışan bir boru hattı oluşturmasıdır, bu nedenle whiledöngüdeki değişken atamaları gibi şeylere boru hattı dışında erişilemez. Bunun bashyolu İşlem Değiştirme'dir :

while read -r line
do
    echo "$line"
done < <(cat "$@")

Bu, whiledöngüyü ana kabukta çalışır halde bırakır , böylece döngüde ayarlanan değişkenlere döngü dışında erişilebilir.


1
Birden fazla dosya hakkında mükemmel nokta . Kaynak ve performans etkilerinin ne olacağını bilmiyorum, ancak bash, ksh veya zsh'de değilseniz ve bu nedenle işlem ikamesini kullanamıyorsanız, burada komut ikamesi olan bir doc'yi deneyebilirsiniz (3'e yayılmış satır) >>EOF\n$(cat "$@")\nEOF. Son olarak, bir kelime oyunu: Perl'de while IFS= read -r linene olduğuna dair daha iyi bir yaklaşımdır ( while (<>)Perl de izini korurken de, önde gelen ve sondaki boşlukları korur \n).
mklement0

4

Perl'in davranışı, OP'de verilen kodla hiçbir veya birkaç argüman alabilir ve bir argüman tek bir tire ise -bu stdin olarak anlaşılır. Dahası, dosya adına sahip olmak her zaman mümkündür $ARGV. Şimdiye kadar verilen cevapların hiçbiri Perl'in bu açıdan davranışını gerçekten taklit etmiyor. İşte saf bir Bash olasılığı. Hile execuygun şekilde kullanmaktır .

#!/bin/bash

(($#)) || set -- -
while (($#)); do
   { [[ $1 = - ]] || exec < "$1"; } &&
   while read -r; do
      printf '%s\n' "$REPLY"
   done
   shift
done

Dosya Adi en uygun yer $1.

Herhangi bir argüman verilmezse, yapay -olarak ilk konumsal parametre olarak ayarladık . Daha sonra parametreleri döngüye sokuyoruz. Bir parametre değilse -, standart girdiyi dosya adından ile yeniden yönlendiririz exec. Bu yönlendirme başarılı olursa, bir whiledöngü ile döngü yaparız . Standart REPLYdeğişkeni kullanıyorum ve bu durumda sıfırlamanıza gerek yok IFS. Başka bir ad istiyorsanız, bu şekilde sıfırlamanız gerekir IFS(tabii ki bunu istemiyorsanız ve ne yaptığınızı bilmiyorsanız):

while IFS= read -r line; do
    printf '%s\n' "$line"
done

2

Daha doğrusu ...

while IFS= read -r line ; do
    printf "%s\n" "$line"
done < file

2
Bunun aslında bir cevap değil stackoverflow.com/a/6980232/45375 hakkında bir yorum olduğunu varsayıyorum . Ekleme: açık yorum yapmak için IFS=ve -r için readher satır okunduğu bu komut aktarımı sağlar değiştirilmemiş (baştaki ve sondaki boşlukları dahil).
mklement0

2

Lütfen aşağıdaki kodu deneyin:

while IFS= read -r line; do
    echo "$line"
done < file

1
Değiştirildiği halde, bunun standart girdiden veya birden fazla dosyadan okunmayacağını, bu nedenle sorunun tam bir cevabı olmadığını unutmayın. (Cevabın ilk gönderilmesinden 3 dakika sonra dakikalar içinde iki düzenleme görmek de şaşırtıcıdır.)
Jonathan Leffler

@JonathanLeffler gibi yaşlı (ve gerçekten iyi) cevabı düzenleme için üzgünüm ... ama bu zavallı görmeye dayanamaz olabilir readolmadan IFS=ve -rve fakir $linesağlıklı tırnak işaretleri olmadan.
gniourf_gniourf

1
@gniourf_gniourf: read -rNotasyonu beğenmedim . IMO, POSIX yanlış anladı; seçenek, ters eğik çizgilerin sonlandırılması için özel anlamı etkinleştirmeli, devre dışı bırakmamalı - böylece mevcut komut dosyaları (POSIX'in varlığından önceki) atlanmadığı -riçin kesilmeyecektir. Bununla birlikte, POSIX kabuğu ve yardımcı programlar standardının en eski versiyonu olan IEEE 1003.2 1992'nin bir parçası olduğunu gözlemliyorum, ancak o zaman bile bir ek olarak işaretlendi, bu yüzden uzun zamandır devam eden fırsatlar hakkında oyalanıyor. Kodum kullanmadığı için asla sorunla karşılaşmadım -r; Şanslı olmalıyım. Beni bu konuda görmezden gel.
Jonathan Leffler

1
@JonathanLeffler Bunun -rstandart olması gerektiğine gerçekten katılıyorum . Kullanmamanın belaya yol açtığı durumlarda olma ihtimalinin düşük olduğunu kabul ediyorum. Yine de, bozuk kod bozuk koddur. Düzenlemem ilk önce $linetırnaklarını kötü şekilde kaçırmış olan fakir değişken tarafından tetiklendi . Ben o sıradayken düzelttim read. Bunu düzeltmedim echoçünkü geri döndürülen bir tür düzenleme. :(.
gniourf_gniourf

1

Kod ${1:-/dev/stdin}sadece ilk argümanı anlayacaktır, peki buna ne dersiniz.

ARGS='$*'
if [ -z "$*" ]; then
  ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
   echo "$line"
done

1

Bu cevapların hiçbirini kabul edilebilir bulmuyorum. Özellikle, kabul edilen cevap sadece ilk komut satırı parametresini işler ve geri kalanını yoksayar. Taklit etmeye çalıştığı Perl programı tüm komut satırı parametrelerini işler. Yani kabul edilen cevap soruya cevap bile vermiyor. Diğer cevaplar bash uzantılarını kullanır, gereksiz 'cat' komutları ekler, yalnızca girdiye çıktıya yankı verme basit durumu için çalışır veya sadece gereksiz derecede karmaşıktır.

Ancak, onlara bir miktar kredi vermeliyim çünkü bana bazı fikirler verdiler. İşte tam cevap:

#!/bin/sh

if [ $# = 0 ]
then
        DEFAULT_INPUT_FILE=/dev/stdin
else
        DEFAULT_INPUT_FILE=
fi

# Iterates over all parameters or /dev/stdin
for FILE in "$@" $DEFAULT_INPUT_FILE
do
        while IFS= read -r LINE
        do
                # Do whatever you want with LINE here.
                echo $LINE
        done < "$FILE"
done

1

Yukarıdaki tüm cevapları birleştirdim ve ihtiyaçlarıma uygun bir kabuk işlevi yarattım. Bu, aralarında paylaşılan bir klasör bulduğum 2 Windows10 makinemin bir cygwin terminalinden. Aşağıdakileri idare edebilmem gerekiyor:

  • cat file.cpp | tx
  • tx < file.cpp
  • tx file.cpp

Belirli bir dosya adı belirtildiğinde, kopyalama sırasında aynı dosya adını kullanmam gerekir. Giriş veri akışı aracılığıyla piped nerede, o zaman saat dakika ve saniye sahip geçici bir dosya adı oluşturmak gerekir. Paylaşılan ana klasörde haftanın günlerinin alt klasörleri bulunur. Bu, organizasyon amaçları içindir.

İşte benim ihtiyaçlarım için nihai senaryo:

tx ()
{
  if [ $# -eq 0 ]; then
    local TMP=/tmp/tx.$(date +'%H%M%S')
    while IFS= read -r line; do
        echo "$line"
    done < /dev/stdin > $TMP
    cp $TMP //$OTHER/stargate/$(date +'%a')/
    rm -f $TMP
  else
    [ -r $1 ] && cp $1 //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
  fi
}

Bunu daha da optimize etmek için görebileceğiniz herhangi bir yol varsa, bilmek istiyorum.


0

Aşağıdakiler standart ile çalışır sh( dashDebian ile test edilmiştir ) ve oldukça okunabilir, ancak bu bir zevk meselesi:

if [ -n "$1" ]; then
    cat "$1"
else
    cat
fi | commands_and_transformations

Ayrıntılar: İlk parametre boş değilse, cato dosya, diğer catstandart girdi. Sonra tüm ififadenin çıktısı tarafından işlenir commands_and_transformations.


IMHO en iyi yanıtı çünkü böylece gerçek çözümü işaret: cat "${1:--}" | any_command. Kabuk değişkenlerini okumak ve yankılamak küçük dosyalar için işe yarayabilir, ancak çok iyi ölçeklenmez.
Andreas Spindler

İçin [ -n "$1" ]basitleştirilebilir [ "$1" ].
agc

0

Bu terminalde kullanımı kolaydır:

$ echo '1\n2\n3\n' | while read -r; do echo $REPLY; done
1
2
3

-1

Peki ya

for line in `cat`; do
    something($line);
done

Çıktısı catkomut satırına yerleştirilir. Komut satırı maksimum boyuttadır. Ayrıca bu satır satır okumaz, kelime kelime okumaz.
Notinlist
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.