Mengapa tidak perintah “ls | file ”bekerja?

32

Saya telah mempelajari tentang baris perintah dan belajar bahwa |(pipa) dimaksudkan untuk mengarahkan output dari perintah ke input yang lain. Jadi mengapa perintah ls | fileitu tidak berhasil?

file input adalah salah satu dari lebih banyak nama file, seperti file filename1 filename2

lsoutput adalah daftar direktori dan file pada folder, jadi saya pikir ls | fileseharusnya menunjukkan tipe file dari setiap file pada folder.

Namun ketika saya menggunakannya, hasilnya adalah:

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

Karena ada beberapa kesalahan dengan penggunaan fileperintah

IanC
sumber
2
Jika Anda menggunakan polos ls, ini menunjukkan bahwa Anda ingin semua file di direktori saat ini ditangani dengan fileperintah. ... Jadi mengapa tidak dilakukan:, file *yang akan membalas dengan baris untuk setiap file, folder.
Knud Larsen
file *adalah cara paling cerdas, saya hanya ingin tahu mengapa menggunakan lsoutput tidak berfungsi. Keraguan dihapus :)
IanC
6
Premisnya salah: "input file adalah salah satu dari lebih banyak nama file, seperti file filename1 filename2" Itu bukan input. Itu adalah argumen baris perintah, seperti yang ditunjukkan oleh @John Kugelman di bawah ini.
Monty Harder
3
Intinya, penguraianls umumnya merupakan ide yang buruk.
kojiro

Jawaban:

71

Masalah mendasar adalah bahwa filemengharapkan nama file sebagai argumen baris perintah, bukan pada stdin. Ketika Anda menulis ls | fileoutput lssedang dikirimkan sebagai input file. Bukan sebagai argumen, sebagai input.

Apa bedanya?

  • Argumen baris perintah adalah ketika Anda menulis flag dan nama file setelah perintah, seperti pada cmd arg1 arg2 arg3. Dalam skrip shell argumen ini tersedia sebagai variabel $1, $2, $3, dll di C Anda akan mengaksesnya melalui char **argvdan int argcargumen untuk main().

  • Input standar, stdin, adalah aliran data. Beberapa program menyukai catatau wcmembaca dari stdin ketika mereka tidak diberi argumen baris perintah. Dalam skrip shell yang dapat Anda gunakan readuntuk mendapatkan satu baris input. Di C Anda dapat menggunakan scanf()atau getchar(), di antara berbagai opsi.

filebiasanya tidak membaca dari stdin. Itu mengharapkan setidaknya satu nama file dilewatkan sebagai argumen. Itu sebabnya ia mencetak penggunaan saat Anda menulis ls | file, karena Anda tidak memberikan argumen.

Anda dapat menggunakan xargsuntuk mengubah stdin menjadi argumen, seperti pada ls | xargs file. Meski begitu, seperti yang disebutkan terdon , penguraian lsadalah ide yang buruk. Cara paling langsung untuk melakukan ini adalah:

file *
John Kugelman mendukung Monica
sumber
2
Atau paksaan fileuntuk mendapatkan nama file dari inputnya, menggunakan ls | file -f -. Masih ide yang buruk.
spektrum
2
@Braiam> Itulah intinya. Dan itu lskeluaran pipa ke filestdin. Cobalah.
spektrum
4
@Braiam> Memang boros dan berbahaya. Tetapi ini berfungsi dan senang untuk membandingkannya dengan opsi yang lebih baik jika OP sedang belajar untuk menggunakan pengalihan. Untuk kelengkapan saya juga bisa menyebutkan file $(ls), yang juga berfungsi, dengan cara lain.
spektrum
2
Saya pikir setelah membaca semua jawaban saya memiliki gambaran yang lebih besar tentang masalah ini, meskipun saya pikir saya perlu membaca lebih lanjut untuk benar-benar memahami semuanya. Pertama, tampaknya menggunakan perpipaan dan pengalihan tidak mem-parsing output sebagai argumen , tetapi sebagai STDIN . Yang saya masih harus membaca lebih lanjut untuk memahami lebih baik, tetapi membuat argumen pencarian dangkal tampak seperti teks yang diuraikan ke program dalam array, dan STDIN seperti cara mengumpulkan informasi untuk file atau output (tidak semua program dirancang untuk bekerja dengan "pooling" ini)
IanC
3
Kedua, menggunakan ls untuk membuat daftar nama file sepertinya ide yang buruk, karena karakter khusus yang diterima pada nama file tetapi dapat berakhir dengan hasil yang menyesatkan pada ls . Karena menggunakan baris baru sebagai pemisah antara nama file dan nama file dapat berisi baris baru dan karakter khusus lainnya, hasil akhirnya mungkin tidak tepat.
IanC
18

Karena, seperti yang Anda katakan, input fileharus berupa nama file . Namun, output dari lshanya teks. Itu kebetulan daftar nama file tidak mengubah fakta bahwa itu hanya teks dan bukan lokasi file pada hard drive.

Ketika Anda melihat output dicetak di layar, apa yang Anda lihat adalah teks. Apakah teks itu puisi atau daftar nama file tidak ada bedanya dengan komputer. Yang ia tahu hanyalah teks. Inilah sebabnya mengapa Anda dapat meneruskan output dari lske program yang menggunakan teks sebagai input (walaupun Anda benar-benar tidak seharusnya ):

$ ls / | grep etc
etc

Jadi, untuk menggunakan output dari perintah yang mencantumkan nama file sebagai teks (seperti lsatau find) sebagai input untuk perintah yang menggunakan nama file, Anda perlu menggunakan beberapa trik. Alat khas untuk ini adalah xargs:

$ ls
file1 file2

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

Seperti yang saya katakan sebelumnya, Anda benar-benar tidak ingin mem-parsing output dari ls. Sesuatu seperti findlebih baik (yang print0mencetak sebuah \0bukannya newilne setelah setiap nama file dan -0dari xargsmemungkinkan itu berurusan dengan masukan tersebut; ini adalah trik untuk membuat perintah pekerjaan Anda dengan nama file yang berisi baris):

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

Yang juga memiliki cara sendiri untuk melakukan ini, tanpa perlu xargssama sekali:

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

Akhirnya, Anda juga dapat menggunakan lingkaran shell. Namun, perhatikan bahwa dalam kebanyakan kasus, xargsakan jauh lebih cepat dan lebih efisien. Sebagai contoh:

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2
terdon
sumber
Sebuah sisi-masalah adalah bahwa filetidak muncul untuk benar-benar membaca stdin kecuali diberi eksplisit -placeholder: membandingkan file foo, echo foo | filedan echo foo | file -; sebenarnya itu mungkin alasan untuk pesan penggunaan dalam kasus OPs (yaitu itu tidak benar-benar karena output dari ls"hanya teks", melainkan karena daftar argumen filekosong)
steeldriver
@steeldriver ya. AFAIK yang berlaku untuk semua program yang mengharapkan file dan bukan teks sebagai input. Mereka mengabaikan stdin secara default. Catatan yang echo foo | file -tidak benar-benar berjalan filepada file footetapi pada aliran stdin.
terdon
Yah ada bebek aneh (?!) Seperti catitu kecuali stdin tanpa -kecuali ketika diberikan argumen file juga saya kira?
steeldriver
3
Jawaban ini gagal menjelaskan perbedaan antara argumen stdin dan argumen command line, dan, meskipun lebih tepat daripada jawaban yang diterima, masih sangat menyesatkan karena alasan yang sama.
zwol
5
@terdon Saya pikir itu kesalahan serius dalam kasus ini. "file (1) mengambil daftar file untuk beroperasi sebagai argumen baris perintah, bukan sebagai input standar" adalah dasar untuk memahami mengapa perintah OP tidak bekerja, dan perbedaannya adalah mendasar untuk shell scripting secara umum; Anda tidak melakukan bantuan apa pun dengan mengabaikannya.
zwol
6

mengetahui bahwa '|' (pipa) dimaksudkan untuk mengarahkan output dari perintah ke input yang lain.

Itu tidak "mengarahkan" output, tetapi mengambil output dari suatu program dan menggunakannya sebagai input, sementara file tidak mengambil input tetapi nama file sebagai argumen , yang kemudian diuji. Pengalihan tidak lulus nama file ini sebagai argumen tidak perpipaan lakukan, nanti apa yang Anda lakukan.

Apa yang dapat Anda lakukan adalah membaca nama file dari file dengan --files-fromopsi jika Anda memiliki file yang berisi daftar semua file yang ingin Anda uji, jika tidak cukup berikan path ke file Anda sebagai argumen.

Braiam
sumber
6

Jawaban yang diterima menjelaskan mengapa perintah pipa tidak bekerja secara langsung, dan dengan file *perintah itu, ia menawarkan solusi yang sederhana dan langsung.

Saya ingin menyarankan alternatif lain yang mungkin berguna pada suatu waktu. Caranya adalah menggunakan (`)karakter backtick . Backtick dijelaskan dengan sangat rinci di sini . Singkatnya, dibutuhkan output dari perintah yang terlampir dalam backticks dan menggantikannya sebagai string ke dalam perintah yang tersisa.

Jadi, find `ls`akan mengambil output dari lsperintah, dan menggantinya sebagai argumen untuk findperintah. Ini lebih panjang dan lebih rumit daripada solusi yang diterima, tetapi varian ini mungkin membantu dalam situasi lain.

Schmuddi
sumber
Saya membaca buku tentang menggunakan baris perintah di Linux (keraguan datang dari saya bereksperimen dengan itu), dan kebetulan saya baru saja membaca tentang "substitusi perintah". Anda dapat menggunakan $ (perintah) atau command(tidak dapat menemukan kode backslash di ponsel saya) untuk memperluas output dari perintah di bash dan menggunakannya sebagai parameter untuk perintah lain. Sangat berguna, meskipun menggunakannya dalam kasus ini (dengan ls ) masih akan menghasilkan beberapa masalah karena karakter khusus pada beberapa nama file.
IanC
@IanC Sayangnya, sebagian besar buku dan tutorial di luar sana tentang bash adalah sampah, tercemar dengan praktik buruk, sintaksis yang sudah usang, bug yang halus; (satu-satunya) referensi yang tepercaya di luar sana adalah pengembang bash, yaitu manual dan saluran #bash IRC di freenode (juga periksa sumber daya yang tertaut dalam topik saluran).
ignis
1
Menggunakan substitusi perintah kadang-kadang bisa sangat membantu, tetapi dalam konteks ini cukup buruk - terutama dengan ls.
Joe
5

Output dari lsmelalui pipa adalah blok data yang solid dengan 0x0a memisahkan setiap baris - yaitu karakter linefeed - dan filemendapatkan ini sebagai satu parameter, di mana ia mengharapkan beberapa karakter untuk bekerja pada satu per satu.

Sebagai aturan umum, jangan pernah gunakan lsuntuk menghasilkan sumber data untuk perintah lain - suatu hari nanti akan disalurkan .. ke rmdan kemudian Anda dalam masalah!

Lebih baik menggunakan loop, seperti for i in *; do file "$i" ; doneyang akan menghasilkan output yang Anda inginkan, dapat diprediksi. Kutipan ada dalam kasus nama file dengan spasi.

Mark Williams
sumber
8
lebih mudah: file *;-)
Wayne_Yux
3
@ IC Aku benar-benar tidak bisa cukup menekankan bahwa parsing output lsadalah ide yang sangat, sangat buruk . Bukan hanya karena Anda mungkin menularkannya ke sesuatu yang berbahaya seperti rm, lebih penting karena itu terpecah pada nama file yang tidak standar.
terdon
5
Paragraf pertama adalah suatu tempat antara menyesatkan dan omong kosong. Umpan baris tidak memiliki relevansi. Paragraf kedua tepat untuk alasan yang salah. Itu buruk untuk menguraikan ls, tetapi bukan karena itu entah bagaimana secara ajaib "disalurkan" ke rm.
John Kugelman mendukung Monica
1
Apakah rmmengambil nama file dari input standar? Saya pikir tidak. Juga, sebagai aturan umum, lstelah menjadi salah satu contoh utama sumber data untuk penggunaan jaringan pipa Unix sejak awal Unix. Itu sebabnya ia default ke satu-nama file-per-line sederhana tanpa atribut atau perhiasan ketika outputnya adalah pipa, tidak seperti format default yang biasa ketika output adalah terminal.
davidbak
2
@DewiMorgan Situs web ini terutama ditujukan untuk audiens non-teknis, jadi menyebarkan / mendorong kebiasaan buruk di sini tidak membahayakan dan tidak ada gunanya. Pada unix.SE atau komunitas teknologi lainnya, yang para penggunanya memiliki pengetahuan / sarana untuk membidik sangat dekat dengan kaki mereka tanpa menembak sendiri, maksud Anda mungkin berlaku (mengenai praktik lain) tetapi di sini tidak membuat komentar Anda terlihat cerdas.
ignis
4

Jika Anda ingin menggunakan pipa untuk memberi makan filegunakan opsi -fyang biasanya diikuti oleh nama file tetapi Anda juga dapat menggunakan tanda hubung tunggal -untuk membaca dari stdin, jadi

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

Trik dengan tanda hubung -berfungsi dengan banyak utils perintah-baris standar (meskipun --kadang - kadang), jadi selalu patut dicoba.

Alat xargini jauh lebih kuat dan dalam banyak kasus hanya diperlukan jika daftar argumen terlalu panjang (lihat posting ini untuk detailnya).

deamentiaemundi
sumber
Kapan itu --? Saya belum pernah melihat itu. --biasanya merupakan indikator "ujung bendera".
John Kugelman mendukung Monica 6-16
Ya, tapi saya menemukannya dalam beberapa contoh (ab) yang digunakan oleh programmer. Saya tidak ingat di mana tepatnya (akan menambahkan komentar jika saya melakukannya) tetapi saya ingat kutukan yang saya ucapkan ketika saya menemukannya dan kutukan ini pasti NSFW ;-)
deamentiaemundi
2

Ini berfungsi menggunakan perintah seperti di bawah ini

ls | xargs file

Ini akan bekerja lebih baik untuk saya

SuperKrish
sumber