Чому команда “ls | файл ”працює?


32

Я вивчав командний рядок і дізнався, що " |трубопровід" призначений для перенаправлення виводу з команди на вхід іншої. То чому команда ls | fileне працює?

file input є однією з більшої кількості імен файлів, як file filename1 filename2

lsвихід - це список каталогів та файлів у папці, тому я вважав, що ls | fileповинен був показати тип файлу кожного файлу в папці.

Коли я ним користуюся, вихід:

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

Оскільки виникла помилка з використанням fileкоманди


2
Якщо ви використовуєте звичайний ls, це означає, що ви хочете, щоб усі файли в поточному каталозі оброблялися fileкомандою. ... То чому б просто не зробити:, file *який відповість рядком для кожного файлу, папки.
Кнуд Ларсен

file *це найрозумніший спосіб, я просто цікавився, чому використання lsвиходу не працює. Сумнів очищений :)
IanC

6
Припущення є помилковим: "Введення файлів є однією з більшіх імен файлів, наприклад, ім'я файлу1 ім'я файлу2" Це не введення. Це аргументи командного рядка, як вказує нижче @John Kugelman.
Monty Harder

3
Тангенціально розбір,ls як правило, погана ідея.
kojiro

Відповіді:


71

Фундаментальне питання полягає в тому, що fileочікує, що імена файлів будуть аргументами командного рядка, а не на stdin. Коли ви пишете, ls | fileрезультат lsпередається як вхід до file. Не як аргументи, як вхідні дані.

Яка різниця?

  • Аргументи командного рядка - це коли ви пишете прапори та імена файлів після команди, як у cmd arg1 arg2 arg3. У скриптах ці аргументи доступні як змінний $1, $2, $3і т.д. У C ви б отримати доступ до їх через char **argvі int argcаргументи main().

  • Стандартний вхід, stdin, - це потік даних. Деякі програми люблять catабо wcчитають з stdin, коли їм не надано жодних аргументів командного рядка. У скрипті оболонки ви можете використовувати readодин рядок введення. В C ви можете використовувати scanf()або getchar(), серед різних варіантів.

fileзазвичай не читається зі stdin. Очікується, що як мінімум одне ім'я файлу буде передано як аргумент. Ось чому він друкує використання, коли ви пишете ls | file, тому що ви не передали аргумент.

Ви можете використовувати xargsдля перетворення stdin в аргументи, як в ls | xargs file. Однак, як згадує Тердон , розбір ls- це погана ідея. Найбільш прямий спосіб зробити це просто:

file *

2
Або змусити fileотримувати імена файлів зі свого вводу, використовуючи ls | file -f -. Ще погана ідея ofc.
спектри

2
@Braiam> У цьому справа. І це lsвиведення труб у filestdin. Спробуй.
спектри

4
@Braiam> Дійсно, це марно і небезпечно. Але це працює, і приємно мати його для порівняння з кращими варіантами, якщо ОП вчиться використовувати перенаправлення. Для повноти я можу також зазначити file $(ls), що також працює, ще в інший спосіб.
спектри

2
Я думаю, що після прочитання всіх відповідей у ​​мене є більш широка картина питання, хоча я думаю, що мені знадобиться додаткове читання, щоб все це зрозуміти. По-перше, мабуть, використовуючи трубопроводи та переадресацію, не аналізується вихід як аргументи , а як STDIN . Який мені ще доводиться читати далі, щоб краще зрозуміти, але створення поверхневих аргументів пошуку здається, що текст аналізується програмою в масиві, а STDIN - як спосіб об'єднання інформації для файлу чи виводу (не всі програми, призначені для робота з цим "об'єднанням")
IanC

3
По-друге, використання ls для складання списку імен файлів здається поганою ідеєю, оскільки спеціальні символи, які прийняті на ім’я файлових файлів, але можуть утворити оманливий вихід на ls . Оскільки він використовує нові рядки як роздільник між іменами і назви файлів, може містити нові рядки та інші спеціальні символи, остаточний висновок може бути неточним.
IanC

18

Тому що, як ви кажете, вхід fileмає бути іменами . Вихід ls, однак, є лише текстом. Те, що трапляється в списку назв файлів, не змінює факту, що це просто текст, а не розташування файлів на жорсткому диску.

Коли ви бачите вихід, надрукований на екрані, то ви бачите текст. Чи цей текст є віршем чи списком імен, це не має значення для комп'ютера. Все, що він знає, це те, що це текст. Ось чому ви можете передавати вихідні lsпрограми програмам, які беруть текст як вхідні дані (хоча вам це справді не слід ):

$ ls / | grep etc
etc

Отже, щоб використовувати вихід команди, яка перераховує назви файлів як текст (наприклад, lsабо find) як вхід для команди, яка приймає назви файлів, вам потрібно скористатися деякими хитрощами. Типовим інструментом для цього є xargs:

$ ls
file1 file2

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

Як я вже говорив, ви, правда, не хочете розбирати результат ls. Що - щось на зразок findкраще (то print0друкує \0замість того , newilne після кожного імені файлу і -0з xargsдозволяє йому мати справу з таким входом, це виверт , щоб зробити ваші команди працюють з іменами файлів , що містять символи нового рядка):

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

У якого також є свій спосіб зробити це, не потребуючи xargsзовсім:

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

Нарешті, ви також можете використовувати петлю оболонки. Однак зауважте, що в більшості випадків xargsце буде набагато швидше і ефективніше. Наприклад:

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

Побічним проблема в тому , що fileне з'являється на самому справі читати стандартний ввід , якщо не дано явне -заповнювачем: порівняти file foo, echo foo | fileі echo foo | file -; насправді, це, мабуть, причина використання повідомлення у випадку з ОП (тобто це не зовсім тому, що вихід ls"просто текст", а скоріше тому, що список аргументів fileпорожній)
steeldriver

@steeldriver так. AFAIK так стосується всіх програм, які очікують файли, а не текст як вхідні дані. Вони просто ігнорують stdin за замовчуванням. Зауважте, що echo foo | file -насправді не працює fileу файлі, fooа у потоці stdin.
terdon

Ну, є такі дивні качки (?!), Як cat, крім stdin, -за винятком випадків, коли даються аргументи файлів, а також я думаю?
steeldriver

3
Ця відповідь не вдається пояснити різницю між аргументами stdin та командного рядка, і тому, незважаючи на те, що вона більш точна, ніж прийнята відповідь, все ще глибоко вводить в оману з тієї ж причини.
zwol

5
@terdon Я думаю, що це серйозна помилка в цьому випадку. "файл (1) приймає список файлів, на яких працює аргумент командного рядка, а не як стандартний ввід", є основним для розуміння того, чому команда ОП не працювала, і відмінність є основоположним для сценарію оболонки взагалі; ви не робите їм жодної прихильності, промальовуючи її.
zwol

6

дізнався, що '|' (трубопровід) призначений для перенаправлення виводу з команди на вхід іншої.

Він не "перенаправляє" вихід, але приймає вихід програми та використовує її як вхідний файл, тоді як файл не бере введення, а назви файлів, як аргументи , які потім тестуються. Перенаправлення не передають ці назви файлів, оскільки це не аргументи, ані трубопроводи , пізніше, що ви робите.

Що ви можете зробити, це прочитати назви файлів з файлу з --files-fromможливістю, якщо у вас є файл, у якому перераховані всі файли, які ви хочете перевірити, інакше просто переведіть шляхи до ваших файлів як аргументи.


6

Прийнята відповідь пояснює, чому команда pipe не працює відразу, і за допомогою file *команди вона пропонує просте, зрозуміле рішення.

Я хотів би запропонувати ще одну альтернативу, яка може стати в нагоді на якийсь час. Трюк - використання (`)символу backtick . Лапки пояснюється дуже докладно тут . Коротше кажучи, він приймає висновок команди, укладеної у задніх полях, і замінює її як рядок на решту команди.

Отже, find `ls`візьмемо висновок lsкоманди та замінимо її як аргументи для findкоманди. Це довше і складніше, ніж прийняте рішення, але варіанти цього можуть бути корисними в інших ситуаціях.


Я читаю книгу про використання командного рядка в Linux (сумніви виникли від мене, експериментуючи з нею), і збіг випадково я просто прочитав про "заміну команди". Ви можете використовувати $ (команду) або command(не можу знайти зворотній косу рису на моєму телефоні), щоб розширити вихід команди в bash і використовувати її як параметр для інших команд. Дійсно корисно, хоча використання цього в цьому випадку (з ls ) все-таки призведе до деяких проблем через особливі символи в деяких іменах.
IanC

@IanC На жаль, більшість книг та навчальних посібників про башти - це сміття, заплямоване поганими звичаями, застарілий синтаксис, тонкі помилки; (Єдиними) надійними посиланнями є розробники bash, тобто посібник та #bash IRC канал на freenode (також перегляньте ресурси, пов'язані в темі каналу).
ignis

1
Використання підстановки команд часом може бути дуже корисним, але в цьому контексті це досить непорочно, особливо з ls.
Джо

також пов’язано: unix.stackexchange.com/a/5782/107266
1717 року

5

Вихід lsчерез трубу являє собою суцільний блок даних, розміром 0x0a, що розділяє кожен рядок, тобто символ передачі рядків, і fileотримує це як один параметр, де він очікує, що кілька символів працюватимуть по одному за один раз.

Як правило, ніколи не використовуйте lsдля генерування джерела даних для інших команд - одного дня він потрапить .. rmі тоді у вас виникнуть проблеми!

Краще використовувати цикл, такий, for i in *; do file "$i" ; doneякий дасть потрібний результат, передбачувано. Цитати є у випадку назви файлів з пробілами.


8
легше: file *;-)
Wayne_Yux

3
@IanC Я дійсно не можу наголосити на тому, що аналіз результатів ls- дуже, дуже погана ідея . Не тільки тому, що ви можете передати його на щось шкідливе, наприклад rm, ще важливіше, тому що воно порушує будь-яке нестандартне ім'я файлу.
terdon

5
Перший абзац знаходиться десь між оманливою та відвертою дурницею. Лінійні стрічки не мають жодної актуальності. Другий абзац правильний з неправильної причини. Погано розбирати ls, але не тому, що це може бути якось магічно "прокладене" в rm.
Джон Кугельман підтримує Моніку

1
Чи rmприймає назви файлів зі стандартного вводу? Я думаю, НЕ. Також, як правило, lsє одним з головних прикладів джерела даних для використання конвеєрів Unix з початку Unix. Ось чому він за замовчуванням використовує просте ім'я одного файлу на рядок без атрибутів і прикрас, коли його вихід - це труба, на відміну від його звичайного форматування за замовчуванням, коли висновком є ​​термінал.
davidbak

2
@DewiMorgan Цей веб-сайт в основному орієнтований на нетехнічну аудиторію, тому розповсюдження / заохочення шкідливих звичок тут не шкодить і не приносить нічого корисного. У unix.SE або іншому технічному співтоваристві, користувачі якого мають знання / засоби націлюватись дуже близько до своїх ніг, не стріляючи в самі ноги, ваша думка може дотримуватися (стосовно інших практик), але тут це не робить ваш коментар виглядати розумним.
ignis

4

Якщо ви хочете використовувати трубу для подачі, fileскористайтеся опцією, за -fякою зазвичай іменем файлу, але ви також можете використовувати один дефіс -для читання з stdin,

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

Трюк з дефісом -працює з багатьма стандартними утилітами командного рядка (хоча це --іноді), тому спробувати завжди варто.

Інструмент xargнабагато потужніший і в більшості випадків потрібен лише в тому випадку, якщо список аргументів занадто довгий (див деталі цій публікації ).


Коли це --? Я ніколи цього не бачив. --зазвичай є індикатором "кінець прапорів".
Джон Кугельман підтримує Моніку

Так, але я виявив це в декількох екземплярах (ab), використовуваних таким чином програмістом. Я не можу пригадати, де саме (додам коментар, якщо це зробити), але я пам’ятаю прокляття, які я вимовив, коли дізнався це, і ці прокляття точно були NSFW ;-)
deamentiaemundi

2

Це працює команда використання, як показано нижче

ls | xargs file

Мені це буде краще працювати


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.