Почему команда «ls | файл »работа?

32

Я изучал командную строку и узнал, что |(конвейер) предназначен для перенаправления вывода команды на ввод другой. Так почему же команда ls | fileне работает?

file вход является одним из нескольких имен файлов, таких как 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команды

IanC
источник
2
Если вы используете обычный ls, это означает, что вы хотите, чтобы все файлы в текущем каталоге обрабатывались fileкомандой. ... Так почему бы просто не сделать:, file *который будет отвечать строкой для каждого файла, папки.
Кнуд Ларсен
file *это самый умный способ, мне просто интересно, почему использование lsвывода не работает. Сомнение прояснилось :)
IanC
6
Предпосылка ошибочна: «Ввод файла - это одно из нескольких имен файлов, например, file filename1 filename2». Это не ввод. Это аргументы командной строки, как @John Kugelman указывает ниже.
Монти Хардер
3
Тангенциально, разборls вообще плохая идея.
Кодзиро

Ответы:

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обычно не читает со стандартного ввода. Ожидается, что в качестве аргумента будет передано хотя бы одно имя файла. Вот почему он печатает использование, когда вы пишете ls | file, потому что вы не передали аргумент.

Вы можете использовать xargsдля преобразования stdin в аргументы, как в ls | xargs file. Тем не менее, как отмечает Тердон , разбор ls- плохая идея. Самый прямой способ сделать это просто:

file *
Джон Кугельман поддерживает Монику
источник
2
Или заставить fileполучать имена файлов из его ввода, используя ls | file -f -. Все еще плохая идея.
спектры
2
@Braiam> В этом все дело. И эти трубы lsвыводятся в стандартный fileввод. Попробуйте это.
спектры
4
@Braiam> Действительно, это расточительно и опасно. Но это работает, и приятно иметь его для сравнения с лучшими вариантами, если OP учится использовать перенаправления. Для полноты я мог бы также упомянуть file $(ls), что также работает, еще одним способом.
спектры
2
Я думаю, что после прочтения всех ответов у меня есть более полное представление о проблеме, хотя я думаю, что мне нужно больше читать, чтобы действительно понять все это. Во-первых, очевидно, что использование конвейера и перенаправления не анализирует вывод как аргументы , а как STDIN . Что мне еще нужно прочитать, чтобы лучше понять, но создание поверхностных аргументов поиска похоже на анализ текста в программе в массиве, а STDIN - на способ объединения информации для файла или вывода (не все программы предназначены для работать с этим "объединением")
IanC
3
Во-вторых, использование ls для составления списка имен файлов кажется плохой идеей из-за специальных символов, которые принимаются в именах файлов, но могут привести к вводящему в заблуждение выводу ls . Поскольку в качестве разделителя между именами файлов используются новые строки, они могут содержать новые строки и другие специальные символы, поэтому конечный вывод может быть неточным.
Ян
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
terdon
источник
Побочным проблема в том , что fileне появляется на самом деле читать стандартный ввод , если не дано явное -заполнителем: сравнить file foo, echo foo | fileи echo foo | file -; на самом деле это, вероятно, и является причиной сообщения об использовании в случае OP (то есть, на самом деле это не потому, что вывод ls«просто текст», а скорее потому, что список аргументов для fileпуст)
steeldriver
@steeldriver да. AFAIK это относится ко всем программам, которые ожидают файлы, а не текст в качестве ввода. Они просто игнорируют стандартный ввод по умолчанию. Обратите внимание, что на echo foo | file -самом деле он запускается не fileв файле, fooа в потоке stdin.
тердон
Ну, есть странные утки (?!), Как catэто, за исключением stdin без, -кроме случаев, когда даются аргументы файла, я думаю?
SteelDriver
3
Этот ответ не объясняет разницу между аргументами stdin и командной строки, и поэтому, несмотря на то, что он более точен, чем принятый ответ, он все еще глубоко вводит в заблуждение по той же причине.
Звол
5
@terdon Я думаю, что это серьезная ошибка в этом случае. «file (1) принимает список файлов для работы в качестве аргументов командной строки, а не в качестве стандартного ввода» - это основополагающее значение для понимания того, почему команда OP не работает, и это различие является основополагающим для сценариев оболочки в целом; Вы не делаете им никаких одолжений, замазывая это.
Звол
6

узнал, что '|' (конвейер) предназначен для перенаправления вывода команды на ввод другой.

Он не «перенаправляет» выходные данные, но принимает выходные данные программы и использует их в качестве входных данных, в то время как файл не принимает входные данные, а имена файлов в качестве аргументов , которые затем проверяются. Перенаправления не передают эти имена файлов в качестве аргументов , как это делает ни конвейер , чем позже вы делаете.

Что вы можете сделать, это прочитать имена файлов из файла с --files-fromопцией, если у вас есть файл, в котором перечислены все файлы, которые вы хотите проверить, в противном случае просто укажите пути к вашим файлам в качестве аргументов.

Braiam
источник
6

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

Я хотел бы предложить другую альтернативу, которая может пригодиться в какое-то время. Хитрость заключается в использовании (`)символа обратного хода . Обратный удар объясняется очень подробно здесь . Короче говоря, он принимает выходные данные команды, заключенные в обратные кавычки, и подставляет их в виде строки в оставшуюся команду.

Итак, find `ls`возьмем вывод lsкоманды и подставим его в качестве аргументов findкоманды. Это длиннее и сложнее, чем принятое решение, но варианты этого могут быть полезны в других ситуациях.

Schmuddi
источник
Я читаю книгу об использовании командной строки в Linux (сомнения возникли из-за того, что я экспериментировал с ней), и по совпадению я только что прочитал о «подстановке команд». Вы можете использовать $ (команда) или command(не могу найти код обратной косой черты в моем телефоне), чтобы расширить вывод команды в bash и использовать ее в качестве параметра для других команд. Действительно полезно, даже если его использование в этом случае (с ls ) все равно приведет к некоторым проблемам из-за специальных символов в некоторых именах файлов.
Ян
@IanC К сожалению, большинство книг и учебных пособий по bash - это мусор, зараженный плохими практиками, устаревшим синтаксисом, скрытыми ошибками; (единственными) заслуживающими доверия ссылками являются разработчики bash, то есть руководство и IRC-канал #bash на freenode (также ознакомьтесь с ресурсами, указанными в теме канала).
Игнис
1
Использование подстановки команд иногда может быть очень полезным, но в этом контексте это довольно извращенно - особенно с ls.
Джо
также связано: unix.stackexchange.com/a/5782/107266
Матф
5

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

Как правило, никогда не используйте lsдля генерации источника данных для других команд - однажды он будет передан по трубопроводу ... rmи тогда у вас проблемы!

Лучше использовать цикл, например, for i in *; do file "$i" ; doneкоторый будет производить желаемый результат, как и ожидалось. Кавычки есть в случае имен файлов с пробелами.

Марк Уильямс
источник
8
проще: file *;-)
Wayne_Yux
3
@IanC Я действительно не могу не подчеркнуть, что анализ выходных данных ls- очень, очень плохая идея . Не только потому, что вы можете передать это чему-то вредному, например rm, что более важно, потому что оно ломается от любого нестандартного имени файла.
тердон
5
Первый абзац где-то между вводящей в заблуждение и откровенной чепухой. Строки не имеют никакого отношения. Второй абзац правильный по неправильной причине. Это плохо для парсинга ls, но не потому, что это может быть каким-то волшебным образом «передано» rm.
Джон Кугельман поддерживает Монику
1
Берет ли rmимена файлов из стандартного ввода? Думаю, нет. Кроме того, как общее правило, lsбыл одним из основных примеров источника данных для использования конвейеров Unix с самого начала Unix. Вот почему по умолчанию он представляет собой простое имя файла на строку без атрибутов или украшений, когда его вывод представляет собой канал, в отличие от его обычного форматирования по умолчанию, когда вывод представляет собой терминал.
Давидбак
2
@DewiMorgan Этот веб-сайт в основном предназначен для нетехнической аудитории, поэтому распространение / поощрение вредных привычек здесь приносит вред и ничего хорошего. В unix.SE или другом техническом сообществе, чьи пользователи обладают знаниями / средствами для прицеливания очень близко к их ногам, не выполняя сами ступни, ваша точка зрения может быть верной (в отношении других практик), но здесь это не делает ваш комментарий выглядеть умным.
Игнис
4

Если вы хотите использовать канал для подачи, fileиспользуйте опцию, за -fкоторой обычно следует имя файла, но вы также можете использовать один дефис -для чтения из stdin, поэтому

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

Трюк с дефисом -работает со многими стандартными утилитами командной строки (хотя --иногда это так), поэтому его всегда стоит попробовать.

Этот инструмент xargгораздо более мощный и в большинстве случаев необходим только в том случае, если список аргументов слишком длинный (подробности см. В этом посте ).

deamentiaemundi
источник
Когда это --? Я никогда этого не видел. --обычно это индикатор «конца флагов».
Джон Кугельман поддерживает Монику
Да, но я нашел это в нескольких случаях (ab), используемых таким образом программистом. Я не могу вспомнить, где именно (добавлю комментарий, если я сделаю), но я помню проклятия, которые я произнес, когда узнал, и эти проклятия были определенно NSFW ;-)
deamentiaemundi
2

Это работает использовать команду, как показано ниже

ls | xargs file

Это будет работать лучше для меня

SuperKrish
источник