“Cat file |” arasındaki fark nedir? ./binary ”ve“ ./binary <dosya ”?


102

(Değiştiremiyorum) bir ikili var ve yapabilirim:

./binary < file

Ben de yapabilirim:

./binary << EOF
> "line 1 of file"
> "line 2 of file"
...
> "last line of file"
> EOF

Fakat

cat file | ./binary

bana bir hata veriyor. Neden bir boru ile çalışmadığını bilmiyorum. Her 3 durumda da, dosyanın içeriği ikili standart girişe verilir (farklı şekillerde):

  1. bash dosyayı okur ve onu ikili stdin'e verir
  2. bash, stdin'den (EOF'ye kadar) satırları okur ve onu ikili stdin'e verir
  3. cat okur ve dosyanın satırlarını stdout'a koyar, bash onları ikili stdin'e yönlendirir

İkili, anladığım kadarıyla bu 3 arasındaki farkı fark etmemelidir. Birisi 3. vakanın neden işe yaramadığını açıklayabilir mi?

BTW: İkili tarafından verilen hata :

20170116 / 125624.689 - U3000011 '' komut dosyası okunamadı, hata kodu '14'.

Fakat asıl sorum şu, bu 3 seçeneğe sahip herhangi bir program için nasıl bir fark var ?

İşte bazı başka detaylar: Ben tekrar denedim strace ve bazı hatalar aslında vardı ESPIPE (Yasadışı aramak) dan lseek ardından EFAULT'tur (Kötü adresi) den okunan sağ hata mesajı önce.

I (geçici dosyaları kullanmadan) bir yakut komut dosyası ile kontrol etmeye çalıştı ikili bir parçasıdır callapi gelen Automic (UC4) .


25
Harika, ikili sisteminize gömülü bir UUOC dedektörü var . Onu istiyorum.
xhienne

4
Hangi işletim sistemi var (yani eğer errno olması gerekiyorsa 14'ün ne olduğunu söyleyebiliriz)?
Stéphane Chazelas

6
Bir programın bu şekilde tepki vermesi mümkün olsa da, bunu yapmak oldukça zor bir programdır. Stdin'den herhangi bir girdiyi bekleyen her çılgın olmayan program stdin bir tty iken çalışması gerekir ve eğer bir tty ve bir dosyayla çalışabiliyorsa, boruları desteklememek için de çok az neden vardır. Muhtemelen programın yazarı geçici bir kanamaya sahipti ve isatty()yanlış görünen her şeyin aranabilir ya da saklanabilir bir dosya olmasına rağmen ...
Henning Makholm

9
Hata kodu 14, EFAULT anlamına gelir. Bildirdiğiniz arabellek geçersizse gerçekleşen bir okumada. Programı bozardım ama verilerin okunması için bir arabellek boyutu elde etmek için dosyanın sonuna gelmeyi beklediğini, aramanın işe yaramadığını ve olumsuz bir boyut tahsis etmeye çalıştığını (kötü bir malloc kullanmadan) kötü bir şekilde ele aldığından şüpheliyim. . Tampon verilen hataları okumak için tamponun geçilmesi geçerli değildir.
Matthew Ife

3
@ xhienne Hayır, içinde bir catengelleyici var. Amaçlanan kullanım gibi, iki dosyayı birleştirmek için kullanamadığınız anlaşılıyor.
jpmc26

Yanıtlar:


150

İçinde

./binary < file

binary'ın stdin dosyası salt okunur modda açık. Not bashdosyayı hiç okumuyor, sadece o yürütür sürecin dosya tanımlayıcı 0 (stdin) okuma için açılır binaryiçinde.

İçinde:

./binary << EOF
test
EOF

Kabuğa bağlı olarak binarystdin, silinmiş bir geçici dosya (AT&T ksh, zsh, bash ...) test\ntarafından kabuğun koyduğu veya bir borunun okuma ucunun içerdiği ( dash, yash; ve kabuk test\nparalel olarak yazacaktır) olacaktır . borunun diğer ucunda). Senin durumunda, kullanıyorsanız bash, geçici bir dosya olurdu.

İçinde:

cat file | ./binary

Kabuğa bağlı olarak, binarystdin bir borunun okuma ucu veya yazma yönünün kapatıldığı (ksh93) ve diğer ucunun catiçeriğini yazan soket çiftinin bir fileucu olacaktır.

Stdin normal bir dosya olduğunda (geçici veya değil), aranabilir. binarybaşına veya sonuna gidebilir, Aynı zamanda, bazı yapmak mmap'e vb, geri sarma ioctl()s(kullanılıyorsa FIEMAP / FIBMAP gibi <>yerine <o, içinde etc / yumruk delikleri kesecek olabilir).

Öte yandan boru ve soket çifti, bir işlemler arası iletişim araçları olan pek bir şey yok binaryyanında yapabilir read(bazı boru özgü gibi bazı işlemler olmasına rağmen veri ing ioctl()üzerlerinde uymaya düzenli dosyalar üzerinde olabilir ler) .

Çoğu kez, bu eksik yeteneği var seekboruları ile çalışırken başarısız olmasına uygulamalar neden / şikayet ama farklı türdeki dosyalar düzenli dosyalar üzerinde geçerli değil diğer sistem çağrıları herhangi biri olabilir (gibi mmap(), ftruncate(), fallocate()) . Linux'ta, /dev/stdinfd 0 bir pipo ya da normal bir dosyadayken açarken davranışlarınızda büyük bir fark var .

Orada sadece aranabilir dosyalarla başa çıkabilen birçok komut vardır , ancak bu durumda, genellikle kendi stdin'lerinde açılan dosyalar için değildir.

$ unzip -l file.zip
Archive:  file.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       11  2016-12-21 14:43   file
---------                     -------
       11                     1 file
$ unzip -l <(cat file.zip)
     # more or less the same as cat file.zip | unzip -l /dev/stdin
Archive:  /proc/self/fd/11
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.
unzip:  cannot find zipfile directory in one of /proc/self/fd/11 or
        /proc/self/fd/11.zip, and cannot find /proc/self/fd/11.ZIP, period.

unzipdosyanın sonunda depolanan dizini okuması ve ardından arşiv üyelerini okumak için dosyanın içinde arama yapması gerekir. Fakat burada, dosya (ilk durumda normal, ikinci sırada yer alan boru), ana öğenin önceden açmış olduğu bir fd'yi miras almak yerine kendisine bir yol argümanı olarak verilir unzipve unzipkendisini açar (tipik olarak 0'dan fd'de). Zip dosyalarını stdin'den okumaz. stdin çoğunlukla kullanıcı etkileşimi için kullanılır.

Kendinizinkini binary, bir terminal emülatöründe çalışan etkileşimli bir kabuğun isteminde yeniden yönlendirme yapmadan çalıştırırsanız, binarystdin, ebeveyninin kendisinden devraldığı ve terminal emülatörünün devraldığı kabuk olacaktır. pty cihazı okuma + yazma modunda açılır (bunun gibi /dev/pts/n).

Bu cihazlar da aranamaz. Yani, binaryterminalden giriş çekerken tamam çalışıyorsa, muhtemelen sorun arama ile ilgili değildir.

Eğer bu 14 errno (sistem çağrılarının başarısız olması ile ayarlanan bir hata kodu) anlamına geliyorsa, çoğu sistemde bu EFAULT( Kötü adres ) olacaktır. read()Yazılabilir değil bir bellek adresine okumak için istenirse sistem çağrısı bu hata ile başarısız olur. Bu, fd'nin verileri noktalardan bir boruya mı yoksa normal bir dosyaya mı okumasından bağımsızdır ve genellikle bir hatayı ( 1) gösterir .

binarymuhtemelen stdin (açık) üzerinde bulunan dosyanın türünü belirler ve fstat()ne normal bir dosya ne de bir tty cihazı olduğunda hataya neden olur.

Uygulama hakkında daha fazla şey bilmeden söylemek zor. Altında çalıştırma strace(veya truss/ veya tuscsisteminizde) eşdeğeri, burada başarısız olursa, sistemin ne aradığını görmemize yardımcı olabilir.


1 Sorunuzla ilgili bir yorumda Matthew Ife tarafından öngörülen senaryo burada çok makul görünüyor. Ondan alıntı:

Verinin okunması için bir arabellek boyutu almanın, aramanın işe yaramadığı gerçeğini kötü bir şekilde ele almasının ve olumsuz bir boyut tahsis etmeyi denemesinin (kötü bir malloc kullanmama) sorununu çözdüğünden şüpheleniyorum. Tampon verilen hataların geçerli olduğunu okumak için tamponun geçilmesi.


14
Çok ilginç ... bu, tarzındaki yönlendirilmiş standart girdilerin ./binary < filearanabilir olduğunu ilk duydum !
David Z,

2
@DavidZ Bu opened bir dosya ve ed olan herhangi bir dosya ile aynı şekilde davranır open. Sadece bir ana süreçten miras kalmıştır, ancak bu çok nadir değildir.
Hobbs

3
Sistem strace veya benzeri bir araç içeriyorsa , ikili sistemin hangi sistemi aradığını kontrol etmek için kullanılabilir.
pabouk

2
"Ayrıca kesebilir, eşleştirebilir, içine delik açabilir vb." - Hayır, hayır. Dosya salt okunur modda açık. Bunu yapmak için programın yazma modunda açılması gerekir. Ancak bunu yazma modunda açamaz, çünkü bunu doğrudan yapmak için bir arayüz yoktur, açık bir dosyaya karşılık gelen "" dizin girişini bulmak için herhangi bir arayüz yoktur (ya da iki tane dişçi veya sıfır varsa?) . Dosyayı sabitlemek ve daha sonra aynı inode numarasına sahip bir nesne için dosya sistemini taramak zorunda kalacak. Bu son derece yavaş olurdu.
Kevin

1
@ StéphaneChazelas: oh doğru, open("/proc/self/fd/0", O_RDWR)silinmiş dosyalar üzerinde bile çalışıyor. Aptal ben: P. echo foo>foo; (sleep 0.5; ll -L /proc/self/fd/0; strace ./a.out; ll -L /proc/self/fd/0) < foo & sleep 0.1 && rm foobağlantısını iptal fooa.out önce gelen yönlendirildi onun Stdin ile çalışır foo.
Peter Cordes

46

İşte Stéphane Chazelas'ın cevabını kullanarak lseek(2)verdiği cevabı gösteren basit bir örnek program :

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    int c;
    off_t off;
    off = lseek(0, 10, SEEK_SET);
    if (off == -1)
    {
        perror("Error");
        return -1;
    }
    c = getchar();
    printf("%c\n", c);
}

Test yapmak:

$ make seek
cc     seek.c   -o seek
$ cat foo
abcdefghijklmnopqrstuwxyz
$ ./seek < foo
k
$ ./seek <<EOF
> abcdefghijklmnopqrstuvwxyz
> EOF
k
$ cat foo | ./seek
Error: Illegal seek

Borular aranmaz ve bir programın borulardan şikayet edebileceği bir yer burası.


21

Boru ve yönlendirme, tabiri caizse, farklı hayvanlardır. Eğer kullandığınız zaman here-docyeniden yönlendirme ( <<) veya stdin'i yönlendirme < (eğer olacak, ya da geçici dosya) aslında bir dosya tanımlayıcı girer ve ikili programların Stdin işaret edilecektir nerede olduğunu - Metin yoktan gelmez.

Özellikle, burada bash'skaynak kodundan bir alıntı , redir.c dosyası (sürüm 4.3):

/* Create a temporary file holding the text of the here document pointed to
   by REDIRECTEE, and return a file descriptor open for reading to the temp
   file.  Return -1 on any error, and make sure errno is set appropriately. */
static int
here_document_to_fd (redirectee, ri)

Bu nedenle, yeniden yönlendirme temelde dosya olarak değerlendirilebildiğinden, ikili dosyalar seek()dosyada herhangi bir bayt atlayarak bunlarda veya dosyada kolayca gezinebilir .

Borular, en az 4096 baytlık yazılan veya daha az atomik olduğu garantili 64 KiB (en azından Linux'ta) tamponları oldukları için aranamazlar, yani serbestçe dolaşamazsınız - sadece sırayla okurlar. Bir keresinde tailpython komutunu kullandım. Yönlendirilirse, mikrosaniyelerde 29 milyon satırlık metin aranabilir, ancak catboru aracılığıyla yapılırsa, yapılacak hiçbir şey yoktur - bu nedenle bunların tümü sırayla okunmalıdır.

Diğer bir olasılık, ikili sistemin özel olarak bir dosyayı açmak isteyebilmesi ve bir borudan girdi almak istememesidir. Genellikle fstat()sistem çağrısı yoluyla yapılır ve girişin bir S_ISFIFOdosya türünden gelip gelmediğini kontrol eder (bu bir boru / adlandırılmış boruyu işaret eder).

Sizin özel ikili sisteminiz, ne olduğunu bilmediğimizden, muhtemelen arama girişiminde bulunur, ancak boru arayamaz. Tam olarak 14 hata kodunun ne anlama geldiğini öğrenmek için belgelerine başvurmanız önerilir.

NOT : Kısa çizgi (Debian Almquist Shell, /bin/shUbuntu'da varsayılan ) gibi bazı kabuklar dahilihere-doc olarak borularla yeniden yönlendirme yapar , bu nedenle aranamayabilir. Mesele aynı kalır - borular sıralıdır ve kolayca yönlendirilemezler ve bunu yapmaya çalışmak hatalara yol açar.


Stephane'nin cevabı, burada-doktorların borularla uygulanabileceğini ve bazı ortak mermilerin de böyle dashyapabileceğini söylüyor. Bu cevap, bash ile gözlenen davranışı açıklar, ancak görünüşe göre bu davranış diğer kabuklarda garanti edilmez.
Peter Cordes

@PeterCordes kesinlikle öyle, ve ben bunu dashsistemimde doğruladım . Daha önce farkında değildim. Gösterdiğiniz için teşekkürler
Sergiy Kolodyazhnyy

Başka bir yorum: fstat()stdin'de boru olup olmadığını kontrol etmek için kullanırsınız . statPathname alır. Ama gerçekten, sadece teşebbüs etmek lseek, bir fd'nin zaten açıldıktan sonra aranabilir olup olmadığını belirlemenin en akıllıca yolu.
Peter Cordes

5

En büyük fark, hata işlemedir.

Aşağıdaki durumda hata bildirilir

$ /bin/cat < z.txt
-bash: z.txt: No such file or directory
$ echo $?
1

Aşağıdaki durumda hata bildirilmez.

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0

Bash ile hala PIPESTATUS kullanabilirsiniz:

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo ${PIPESTATUS[0]}
1

Ancak yalnızca komutun yürütülmesinden hemen sonra kullanılabilir:

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0
$ echo ${PIPESTATUS[0]}
0
# oops !

İkili yerine kabuk işlevlerini kullandığımız zaman başka bir fark daha var. In bash(eğer son boru hattı bileşeni hariç bir boru hattı parçası alt kabuklarda yürütülür olan fonksiyonlar, lastpipeseçeneği etkin ve bashetkileşimli olmayan), böylece değişkenlerin değişim Üst kabuğa hiçbir etkileri vardır:

$ a=a
$ b=b
$ x(){ a=x;}
$ y(){ b=y;}

$ echo $a $b
a b

$ x | y
$ echo $a $b
a b

$ cat t.txt | y
$ echo $a $b
a b

$ x | cat
$ echo $a $b
a b

$ x < t.txt
$ y < t.txt
$ echo $a $b
x y

4
Yani, hata işlemenin >kabuk tarafından yapıldığını, ancak boruyla metin üreten komutla yapıldığını gösteriyorsunuz. TAMAM. Fakat bu özel soruda OP mevcut bir dosyayı kullanıyor, bu yüzden sorun bu değil ve açıkça üretilen hata ikili.
Sergiy Kolodyazhnyy

1
Çoğunlukla konunun yanında olsa da, bu cevabın genel olarak bu soru-cevap ile bir ilgisi vardır ve çoğunlukla doğrudur, bu yüzden bu aşırılıkları hak ettiğini sanmıyorum.
Stéphane Chazelas

@Serg: Bir komut satırı olarak kabuk kullandığınızda, bu önemli değildir. Ancak komut dosyalarında, hataların ele alınması çok önemli olabilir.
Vouze
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.