Neden “ls | dosya ”iş?


32

Komut satırı hakkında çalışıyorum ve |(pipeline) çıktısını bir komuttan diğerinin girişine yönlendirmek anlamına geldiğini öğrendim . Peki neden komut ls | fileçalışmıyor?

file giriş, gibi daha fazla dosya adından biridir file filename1 filename2

lsçıktı, bir klasördeki dizinlerin ve dosyaların bir listesidir, bu yüzden ls | fileher dosyanın dosya türünü bir klasörde göstermesi gerektiğini düşündüm .

Ancak kullandığımda çıktı:

    Usage: file [-bcEhikLlNnprsvz0] [--apple] [--mime-encoding] [--mime-type]
        [-e testname] [-F separator] [-f namefile] [-m magicfiles] file ...
    file -C [-m magicfiles]
    file [--help]

fileKomutun kullanımında bazı hatalar olduğu için


2
Düz kullanıyorsanız ls, geçerli dizindeki tüm dosyaların filekomutla işlenmesini istediğinizi belirtir . ... Öyleyse neden sadece yapmıyorsunuz ki: file *her dosya için bir satır ile cevap verecektir, klasör.
Knud Larsen

file *en akıllı yol, sadece lsçıktı kullanmanın neden işe yaramadığını merak ediyordum . Şüphe temizlendi :)
IanC

6
Önceden kusurlu: "dosya girişi, dosya adı1 dosyaadı2 gibi daha fazla dosya adından biri" Bu girdi değil. Bunlar, John Kugelman'ın işaret ettiği gibi, komut satırı argümanlarıdır.
Monty Harder

3
Teğetsel olarak ayrıştırmals genellikle kötü bir fikirdir.
kojiro

Yanıtlar:


71

Temel sorun, filedosya adlarını stdin'de değil, komut satırı argümanları olarak beklemesidir. Yazarken ls | fileçıktı lsolarak girişine geçiliyor file. Argüman olarak değil, girdi olarak.

Fark ne?

  • Komut satırı argümanları, bir komuttan sonra olduğu gibi bayrakları ve dosya adlarını yazarken kullanılır cmd arg1 arg2 arg3. Kabuk komut olarak bu argümanlar değişkenler olarak mevcuttur $1, $2, $3vb yılında C Eğer aracılığıyla bunlara erişmek istiyorum char **argvve int argcargümanlar için main().

  • Standart girdi, stdin, bir veri akışıdır. Herhangi bir komut satırı argümanı verilmediğinde bazı programlar stdin'den hoşlanır catveya wcokunur. Bir kabuk betiğinde readtek bir satır girdi almak için kullanabilirsiniz . C içinde scanf()veya getchar()çeşitli seçenekler arasında kullanabilirsiniz .

filenormalde stdin'den okumaz. En az bir dosya adının argüman olarak geçirilmesini bekliyor. Bu yüzden yazarken kullanımı basar ls | file, çünkü bir argüman iletmediniz.

xargsStdin'i olduğu gibi argümanlara dönüştürmek için kullanabilirsiniz ls | xargs file. Yine de, terdon'un belirttiği gibi , ayrıştırma lskötü bir fikirdir. Bunu yapmanın en doğrudan yolu basitçe:

file *

2
Veya filekullanarak dosya adlarını girişten almaya zorlayın ls | file -f -. Hala kötü bir fikir.
spektrumlar

2
@Braiam> Mesele bu. Ve bu boruların lsçıkışı file's' s stdin. Denemek.
spektrumlar

4
@Braiam> Gerçekten israf ve tehlikeli. Ancak, OP yeniden yönlendirme kullanmayı öğreniyorsa işe yarar ve daha iyi seçeneklerle karşılaştırmak güzel olur. Bütünlüğü sağlamak için file $(ls)ayrıca çalıştığını da belirtebilirim , başka bir şekilde.
spectras

2
Tüm cevapları okuduktan sonra konuyla ilgili daha büyük bir resme sahibim, sanırım hepsini anlamak için daha fazla okumaya ihtiyacım olacak. İlk olarak, görünüşe göre borulama ve yönlendirmeyi kullanmak çıktıyı argüman olarak değil, STDIN olarak ayrıştırır . Daha iyi anlamak için hala daha fazla okumam gereken, ancak yüzeysel bir arama argümanları yapmak, bir dizideki programa ayrıştırılmış metin gibi görünüyor ve bir dosya veya çıktı için bilgi biriktirme yolu gibi STDIN (tüm programlar tasarlanmamış bu "havuz" ile çalışmak)
IanC

3
İkincisi, kullanan ls dosya adları bir listesini yapmak için çünkü dosya adlarına kabul edilir ama bir yanıltıcı çıktı de gidebilirler özel karakterler, kötü bir fikir gibi görünüyor ls . Yeni isimleri dosya isimleri ve dosya isimleri arasında bir ayırıcı olarak kullandığından, yeni çizgiler ve diğer özel karakterler içerebilir , nihai çıktı kesin olmayabilir.
IanC

18

Dediğiniz gibi, Çünkü giriş fileolmak zorunda dosya adları . lsBununla birlikte, çıktı sadece metindir. Dosya adlarının bir listesi olması, sabit diskteki dosyaların yerini değil, yalnızca metin olduğu gerçeğini değiştirmez.

Ekranda çıktı çıktı gördüğünüzde gördüğünüz metindir. Bu metnin bir şiir veya bir dosya listesi olup olmadığı bilgisayar için bir fark yaratmaz. Tek bildiği metin olduğu. Bu nedenle, çıktı olarak lsmetin alan programların çıktısını alabilirsiniz ( gerçekten, gerçekten yapmamalısınız ):

$ ls / | grep etc
etc

Bu nedenle, dosya adlarını alan bir komutun girişi olarak dosya adlarını metin olarak ( lsveya veya gibi find) listeleyen bir komutun çıktısını kullanmak için bazı hileler kullanmanız gerekir. Bunun için tipik bir araçtır xargs:

$ ls
file1 file2

$ ls | xargs wc
 9  9 38 file1
 5  5 20 file2
14 14 58 total

Daha önce de söylediğim gibi, aslında, çıktısını ayrıştırmak istemezsiniz ls. Böyle bir şey finddaha iyidir ( print0baskılar bir \0yerine her dosya adından sonra bir newilne ve -0ait xargsböyle girdi başa sağlar; bu dosya adları yeni satır içeren ile komutları çalışması için bir hiledir):

$ find . -type f -print0 | xargs -0 wc
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

Ayrıca, buna ihtiyaç duymadan xargs, bunu yapmanın kendi yolu var :

$ find . -type f -exec wc {} +
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

Son olarak, bir kabuk döngüsü de kullanabilirsiniz. Ancak, çoğu durumda, xargsçok daha hızlı ve daha verimli olacağını unutmayın . Örneğin:

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2

Bir yan mesele olduğunu fileaçık bir verilen sürece aslında stdin'i okumak görünmüyor -tutucudur: Karşılaştırma file foo, echo foo | fileve echo foo | file -; Aslında OP'lerdeki kullanım mesajının nedeni muhtemelen budur (yani, çıktının ls"basitçe metin" olması nedeniyle değil, bunun yerine argüman listesinin fileboş olması nedeniyle değil )
steeldriver

@steeldriver evet. AFAIK, giriş olarak metin değil, dosya bekleyen tüm programlar için geçerlidir. Varsayılan olarak sadece stdin'i yok sayarlar. echo foo | file -Aslında filedosyada foodeğil, stdin akışında çalıştırılmadığını unutmayın .
terdon

Peki sanırım bunun gibi garip ördekler (?!) Gibi catstdin -dışında dosya argümanları verilmediği sürece bence?
steeldriver

3
Bu cevap, stdin ve komut satırı argümanları arasındaki farkı açıklayamaz ve bu nedenle, kabul edilen cevaptan daha fazla noktada olmasına rağmen, aynı nedenden dolayı hala derinden yanıltıcıdır.
zwol

5
@ terdon Bu durumda ciddi bir hata olduğunu düşünüyorum. "dosya (1), standart giriş olarak değil, komut satırı argümanları olarak çalışacak dosyaların listesini alır", OP komutunun neden işe yaramadığını anlamak için esastır ve ayrım genel olarak kabuk komut dosyası çalıştırmada temeldir; üzerlerine bakarak hiçbir iyilik yapmıyorsunuz.
zwol

6

'|' öğrendim (pipeline) çıktısını bir komuttan diğerinin girişine yönlendirmek içindir .

Çıktıyı "yeniden yönlendirmez", ancak bir programın çıktısını alır ve onu girdi olarak kullanır, dosya ise girdileri değil, dosya isimlerini argümanlar olarak alır , sonra test edilir. Yönlendirmeler, bu dosya adlarını , daha sonra ne yaptığınızı, ne borulama ne de argüman olarak iletmez.

Yapmanız gereken, --files-fromsınamak istediğiniz tüm dosyaları listeleyen bir dosyanız varsa , seçeneğin bulunduğu bir dosyadaki dosya adlarını okumaktır , aksi takdirde dosyaları dosyalara argüman olarak iletirsiniz.


6

Kabul edilen cevap, neden boru komutunun derhal çalışmadığını açıklar ve file *komut ile birlikte basit ve anlaşılır bir çözüm sunar.

Bir ara işe yarayacak başka bir alternatif önermek istiyorum. Hile backtick (`)karakterini kullanıyor . Ters tırnak ayrıntılı olarak açıklanmıştır burada . Kısacası, geri tepmelerdeki komutun çıktısını alır ve geri kalan komuta dizge olarak koyar.

Böylece, komutun find `ls`çıktısını alacak lsve onu findkomutun argümanları olarak kullanacaktır . Bu kabul edilen çözümden daha uzun ve daha karmaşık, ancak bunun varyantları başka durumlarda yardımcı olabilir.


Linux'ta komut satırını kullanma hakkında bir kitap okuyorum (şüphe deneyimlemekten geldi) ve tesadüf eseri sadece "komut değiştirme" hakkında okudum. Ya kullanabilirsiniz $ (komut) veya commandbash bir komutun çıktısını genişletmek ve diğer komutlara parametre olarak kullanmak için (telefonumda ters eğik çizgi kodunu bulamıyorum). Gerçekten yararlı, bu durumda kullanmakla birlikte ( ls ile ), bazı dosya adlarındaki özel karakterler nedeniyle bazı sorunlara neden olur.
IanC

@IanC Maalesef, bash ile ilgili birçok kitap ve öğreticinin çoğu, kötü uygulamalar, kullanımdan kaldırılmış sözdizimi, ince hatalarla dolu çöplerdir; (sadece) güvenilir referanslara freenode üzerindeki bash geliştiricileri, yani kılavuz ve #bash IRC kanalı var (ayrıca kanal konusuna bağlı kaynakları kontrol edin).
ignis

1
Komut değiştirme kullanımı zaman zaman gerçekten yararlı olabilir, ancak bu bağlamda oldukça sapkın - özellikle ls ile.
Joe,


5

lsBir borudan çıkan çıktı, her satırı - yani satır beslemeli bir karakteri - ayıran 0x0a değerine sahip katı bir veri bloğudur ve filebunu, bir kerede birden fazla karakterin çalışmasını beklediği tek bir parametre olarak alır.

Genel bir kural olarak, lsdiğer komutlar için bir veri kaynağı oluşturmak için asla kullanmayın - bir gün yayınlanacak .. rmve sonra başınız belada!

Tahminen, for i in *; do file "$i" ; doneistediğiniz çıktıyı üretecek bir döngü kullanmak daha iyidir. Alıntılar, boşluk içeren dosya isimleri durumunda vardır.


8
daha kolay: file *;-)
Wayne_Yux

3
@IanC Gerçekten çıktısını ayrıştırma yeterince stres değildir lsbir olduğunu çok, çok kötü bir fikir . Sadece rm, standart olmayan herhangi bir dosya adını kırdığı için daha önemlisi gibi zararlı bir şeye aktarabileceğinizden değil .
terdon

5
İlk paragraf yanıltıcı ve saçma sapan bir yerde. Satır yemlerinin alaka düzeyi yoktur. İkinci paragraf yanlış sebep için doğru. Ayrıştırmak kötü, ama bir şekilde sihirli bir şekilde rm'ye "bağlanmış" olabileceğinden değil.
John Kugelman,

1
Does rmstandart girdiden dosya almak? Bence değil. Ayrıca, genel bir kural lsolarak, Unix'in başlangıcından beri Unix boru hatlarının kullanımı için bir veri kaynağının ana örneklerinden biri olmuştur. Bu nedenle, çıkış terminal olduğunda normal varsayılan biçimlendirmenin tersine, çıkış bir boru olduğunda öznitelik veya süslemesiz satır başına basit bir dosya adı başına varsayılandır.
davidbak

2
@DewiMorgan Bu web sitesi esas olarak teknik olmayan bir kitleye yöneliktir, bu nedenle kötü alışkanlıkları yaymak / teşvik etmek zarar verir ve hiçbir şey yapmaz. Unix.SE'de veya kullanıcıları ayaklarını vurmadan ayaklarına çok yakın durmayı amaçlayan bilgi / araçlara sahip olan diğer teknoloji topluluklarında, amacınız (diğer uygulamalarla ilgili olarak) dikkat çekebilir, ancak burada yorumunuzu akıllı göstermez.
ignis

4

Beslemek için bir boru kullanmak istiyorsanız , normalde bir dosya adı izleyen fileseçeneği kullanın, -fancak -stdin'den okumak için tek bir kısa çizgi de kullanabilirsiniz.

$ ls
cow.pdf  some.txt
$ ls | file -f -
cow.pdf:       PDF document, version 1.4
some.txt:        ASCII text

Kısa -çizgili numara, birçok standart komut satırı yardımcı programı ile çalışır ( --bazen olmasına rağmen ), bu yüzden her zaman denemeye değer.

Araç xargçok daha güçlü ve çoğu durumda sadece argüman listesi çok uzunsa gereklidir ( ayrıntılar için bu yazıya bakın).


O zaman olduğunu --? Bunu hiç görmedim. --genellikle "bayrakların sonu" göstergesidir.
John Kugelman

Evet, ancak programcı tarafından bu şekilde kullanılan birkaç örnekte (ab) buldum. Tam olarak nerede olduğunu hatırlayamıyorum (yaparsam yorum ekleyeceğim) ama öğrendiğim zaman küfürlerimi hatırlıyorum ve bu küfürler kesinlikle NSFW idi ;-)
deamentiaemundi

2

Aşağıdaki gibi kullanım komutunu çalışır

ls | xargs file

Benim için daha iyi çalışacak


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.