Выполнить две команды для одного аргумента (без сценариев)

28

Как я могу выполнить две команды на одном входе, не вводя этот ввод дважды?

Например, команда stat много говорит о файле, но не указывает его тип файла:

stat fileName

Команда file сообщает тип файла:

file fileName

Вы можете выполнить это в одну строку следующим образом:

stat fileName ; file fileName

Тем не менее, вы должны ввести fileName дважды.

Как вы можете выполнить обе команды на одном и том же входе (не вводя ввод или переменную ввода дважды)?

В Linux я знаю, как транслировать выходные данные, но как вы транслируете входные данные?

Lonniebiz
источник
14
Просто чтобы быть понятным, это не «входы», это аргументы (или параметры). Ввод может быть передан через <(ввод из файла на левую сторону) или |(ввод из потока на правую сторону). Есть разница
Златовласка
6
Не ответ на ваш вопрос, но, возможно, ответ на вашу проблему: в bash yank-last-argкоманда (сочетание клавиш по умолчанию Meta-.или Meta-_; Metaчасто это левая Altклавиша) скопирует последний параметр из предыдущей командной строки в текущую. Таким образом, после выполнения stat fileNameвы набираете file [Meta-.]и не нужно вводить это fileNameснова. Я всегда этим пользуюсь.
Дабу
2
И разница в том, что <и >есть перенаправление , а не пайпинг . Конвейер соединяет два процесса, а перенаправление просто переназначает stdin / stdout.
bsd
@bdowning: Да, но вы перенаправляете канал, хотя не так ли. Вы можете перенаправить начало конвейера для чтения из файла на диске, или вы можете перенаправить конец конвейера для записи в файл на диске. Все еще обжигаю. ;-)
Джеймс Хай,
Связанный (но более специализированный): есть ли однострочник, который позволяет мне создавать каталог и одновременно перемещаться в него?
G-Man говорит: «Восстановите Монику»

Ответы:

22

Вот еще один способ:

$ stat filename && file "$_"

Пример:

$ stat /usr/bin/yum && file "$_"
  File: ‘/usr/bin/yum’
  Size: 801         Blocks: 8          IO Block: 4096   regular file
Device: 804h/2052d  Inode: 1189124     Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:rpm_exec_t:s0
Access: 2014-06-11 22:55:53.783586098 +0700
Modify: 2014-05-22 16:49:35.000000000 +0700
Change: 2014-06-11 19:15:30.047017844 +0700
 Birth: -
/usr/bin/yum: Python script, ASCII text executable

Это работает в bashи zsh. Это также работает mkshи dashтолько в интерактивном режиме. В AT & T ksh это работает только тогда, когда он file "$_"находится на линии, отличной от той stat.

cuonglm
источник
1
Один !$не будет работать, потому что !$расширяется до последнего слова последнего события истории (то есть ранее введенной командной строки, а не команды stat).
Стефан Шазелас
@ StéphaneChazelas: О да, моя ошибка, я удалил ее.
cuonglm
32

Вероятно, я собираюсь получить мои пальцы за это, вот хакерская комбинация расширения bash brace и eval, которая, кажется, добивается цели

eval {stat,file}" fileName;"
Iruvar
источник
8
Извините, но я не могу удержаться .
Андреас Визе
2
расширение скобки возникло в csh, а не bash. bashскопировал это из ksh. Этот код будет работать во всех csh, tcsh, ksh, zsh, fish и bash.
Стефан Шазелас
1
Жаль, что вы теряете возможность «убрать» (автозаполнение) fileName из-за цитаты.
Lonniebiz
Извините, но я не мог сопротивляться либо
tomd
извините, в чем здесь проблема eval? (со ссылкой на топ комментариев)
user13107
28

С помощью zshвы можете использовать анонимные функции:

(){stat $1; file $1} filename

С esлямбдами:

@{stat $1; file $1} filename

Вы также можете сделать:

{ stat -; file -;} < filename

(делая statпервый, как fileобновит время доступа).

Я сделаю:

f=filename; stat "$f"; file "$f"

хотя, для этого и нужны переменные.

Стефан Шазелас
источник
3
{ stat -; file -;} < filenameэто магия statДолжны ли проверять, чтобы его стандартный ввод был подключен к файлу, и сообщать об атрибутах файла? Предположительно не продвигает указатель на stdin, поэтому позволяет file
прослушивать
1
@ 1_CR, да. stat -говорит, statчтобы сделать fstat()на его fd 0.
Стефан Chazelas
4
{ $COMMAND_A -; $COMMAND_B; } < filenameВариант не будет работать в общем случае , если $ COMMAND_A фактически читает любой входной сигнал; Например { cat -; cat -; } < filename, напечатает содержимое файла только один раз.
Макс Нанаси
2
stat -обрабатывается специально начиная с coreutils-8.0 (октябрь 2009 г.), в версиях, предшествующих этому, вы получите ошибку (если у вас нет файла, вызванного -из предыдущего эксперимента, и в этом случае вы получите неправильные результаты ...)
mr .spuratic
1
@ mr.spuratic. Да, и BSD, IRIX и zsh stats также не распознают это. С zsh и IRIX stat вы можете использовать stat -f0вместо этого.
Стефан Шазелас
9

Все эти ответы кажутся мне сценариями в большей или меньшей степени. Для решения, которое на самом деле не требует сценариев и предполагает, что вы сидите за клавиатурой и набираете текст в оболочке, вы можете попробовать:

stat somefile Enter
file Esc_   Enter

По крайней мере, в bash, zsh и ksh, в режимах vi и emacs, нажатие Escape и подчеркивание вставляет последнее слово из предыдущей строки в буфер редактирования для текущей строки.

Или вы можете использовать подстановку истории:

stat somefile Enter
file! $Enter

В bash, zsh, csh, tcsh и большинстве других оболочек !$заменяется последним словом предыдущей командной строки при анализе текущей командной строки.

godlygeek
источник
какие другие опции доступны в zsh / bash, которые похожи Esc _? Можете ли вы связать с официальной документацией?
Абхиджит Растоги
1
Bash: gnu.org/software/bash/manual/bashref.html#Command-Line-Editing и поиск «M-_»
godlygeek
1
zsh: linux.die.net/man/1/zshzle и ищите «insert-last-word»
godlygeek
1
@shadyabhi ^ Ссылки для стандартных комбинаций клавиш bash и zsh. Обратите внимание, что bash просто использует readline, поэтому (большинство) bash будут работать в любом приложении, которое использует библиотеку readline.
Godlygeek
3

Я нашел способ сделать это достаточно просто, используя xargs, он берет файл / канал и преобразует его содержимое в аргументы программы. Это может быть объединено с тройником, который разделяет поток и отправляет его двум или более программам. В вашем случае вам нужно:

echo filename | tee >(xargs stat) >(xargs file) | cat

В отличие от многих других ответов, это будет работать в bash и большинстве других оболочек под Linux. Я предполагаю, что это хороший вариант использования переменной, но если вы абсолютно не можете ее использовать, то это лучший способ сделать это только с помощью конвейеров и простых утилит (при условии, что у вас нет особо красивой оболочки).

Кроме того, вы можете сделать это для любого количества программ, просто добавив их таким же образом, как эти две после тройника.

РЕДАКТИРОВАТЬ

Как указывается в комментариях, в этом ответе есть несколько недостатков, во-первых, это возможность чересстрочного вывода. Это можно исправить так:

echo filename | tee >(xargs stat) >(( wait $!; xargs file )) | cat

Это заставит команды выполняться по очереди, и выходные данные никогда не будут чересстрочными.

Вторая проблема состоит в том, чтобы избежать замены процесса, которая недоступна в некоторых оболочках, это может быть достигнуто путем использования tpipe вместо tee следующим образом:

echo filename | tpipe 'xargs stat' | ( wait $!; xargs file )

Это должно переноситься практически на любую оболочку (я надеюсь) и решает проблемы в других комментариях, однако я пишу последний из памяти, так как в моей нынешней системе нет доступных tpipe.

Vality
источник
1
Подстановка процессов - это функция ksh, которая работает только в ksh(не в публичном доступе), bashи zsh. Обратите внимание, что statи fileбудет работать одновременно, поэтому, возможно, их выходные данные будут переплетены (я полагаю, вы используете | catпринудительную буферизацию вывода для ограничения этого эффекта?).
Стефан Шазелас
@ StéphaneChazelas Вы правы насчет cat, при его использовании мне не удавалось создать случай чересстрочного ввода при его использовании. Однако я собираюсь попробовать что-нибудь, чтобы на мгновение создать более портативное решение.
Vality
3

Этот ответ похож на пару других:

имя   файла статистики && file! #: 1

В отличие от других ответов, которые автоматически повторяют последний аргумент из предыдущей команды, этот требует подсчета:

ls -ld filename   && file! #: 2

В отличие от некоторых других ответов, это не требует кавычек:

stat "useless cat"  &&  file !#:1

или

echo Sometimes a '$cigar' is just a !#:3.
Скотт
источник
2

Это похоже на пару других ответов, но является более переносимым, чем этот, и намного проще, чем этот :

sh -c 'stat "$ 0"; Файл «$ 0»» имя файла

или

sh -c 'stat "$ 1"; Файл «$ 1»»ш имя файла

в котором shназначен $0. Несмотря на то, что для ввода текста требуется больше трех символов, эта (вторая) форма предпочтительнее, потому что $0это имя, которое вы даете этому встроенному сценарию. Он используется, например, в сообщениях об ошибках в оболочке для этого сценария, например , когда fileили statне может быть найден или не может быть выполнен по любой причине.


Если вы хотите сделать

stat имя файла 1  имя файла 2  имя файла 3 …; файл имя_файла 1  имя_файла 2  файла 3 ...

для произвольного числа файлов сделайте

sh -c 'stat "$ @"; файл "$ @" 'sh имя файла 1  имя файла 2  имя файла 3

который работает, потому что "$@"эквивалентен "$1" "$2" "$3" ….

Скотт
источник
Это $0имя, которое вы хотите дать этому встроенному скрипту. Например, он используется в сообщениях об ошибках оболочкой для этого скрипта. Например, когда fileили statне могут быть найдены или не могут быть выполнены по любой причине. Итак, вы хотите что-то значимое здесь. Если не вдохновили, просто используйте sh.
Стефан Шазелас
1

Я думаю, что вы задаете неправильный вопрос, по крайней мере, для того, что вы пытаетесь сделать. statи fileкоманды принимают параметры, которые представляют собой имя файла, а не его содержимое. Позднее команда выполняет чтение файла, идентифицируемого указанным вами именем.

Предполагается, что канал имеет два конца, один используется в качестве входных данных, а другой - в качестве выходных, и это имеет смысл, так как вам, возможно, придется выполнять разную обработку в процессе работы с различными инструментами, пока вы не получите нужный результат. Сказать, что вы знаете, как передавать данные по выходам, но не знаете, как передавать данные по входам, в принципе неправильно.

В вашем случае вам вообще не нужен канал, поскольку вы должны предоставить входные данные в форме имени файла. И даже если эти инструменты ( statи file) будут читать stdin, труба здесь снова не актуальна, так как ввод не должен изменяться каким-либо инструментом, когда он попадает во второй.

vadimbog
источник
1

Это должно работать для всех имен файлов в текущем каталоге.

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *
mikeserv
источник
1
Предполагая, что имена файлов не содержат двойных кавычек, обратной косой черты, доллара, обратной черты, }... символов и не начинаются с -. Некоторые printfреализации (zsh, bash, ksh93) имеют %q. При том zsh, что это может быть:printf 'stat %q; file %1$q\n' ./* | zsh
Стефан Шазелас
@StephaneChezales - все это исправлено, за исключением возможности жесткой цитаты. Я мог бы справиться с этим с одним, sed s///я думаю, но это о сфере действия - и если люди помещают это в свои имена файлов, они должны использовать окна.
mikeserv
@ StéphaneChazelas - неважно - я забыл, как легко с ними обращаться.
mikeserv
Строго говоря, это не ответ на вопрос: «Как вы можете выполнить обе команды на одном и том же вводе ( без ввода ввода… дважды )?» (Выделение добавлено). Ваш ответ относится ко всем файлам в текущем каталоге - и включает в себя *дважды . Вы могли бы также сказать stat -- *; file -- *.
Скотт
@Scott - ввод не набирается дважды - *расширяется. Расширение на *плюс две команды для каждого аргумента в * это вход. Видеть? printf commands\ %s\;shift *
mikeserv
1

Здесь есть несколько разных ссылок на «входные данные», поэтому я приведу несколько сценариев с пониманием этого в первую очередь. Для быстрого ответа на вопрос в кратчайшей форме :

stat testfile < <($1)> outputfile

Вышеприведенное будет выполнять статистику для тестового файла, принимать (перенаправлять) его STDOUT и включать его в следующую специальную функцию (часть <()), а затем выводить окончательные результаты, что бы это ни было, в новый файл (выходной файл). Файл вызывается, затем на него ссылаются встроенные команды bash ($ 1 каждый раз после, пока вы не начнете новый набор инструкций).

Ваш вопрос великолепен, и есть несколько ответов и способов сделать это, но он действительно меняется в зависимости от того, что вы делаете конкретно.

Например, вы можете зациклить это, что довольно удобно. Обычное использование этого в псевдо-коде мышления:

run program < <($output_from_program)> my_own.log

Принимая это и расширяя эти знания, вы можете создавать такие вещи, как:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

Это выполнит простую команду ls -A в вашем текущем каталоге, а затем покажет цикл в цикле по каждому результату от ls -A до (и здесь это сложно!) Grep «thatword» в каждом из этих результатов и выполнит только предыдущий printf (красным), если он действительно обнаружил файл с этим словом. Он также запишет результаты grep в новый текстовый файл files_that_matched_thatword.

Пример вывода:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

index.html

Все это просто напечатало результат ls -A, ничего особенного. На этот раз добавьте что-нибудь для grep:

echo "thatword" >> newfile

Теперь перезапустите это:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

files_that_matched_thatword  index.html  newfile

Found a file: newfile:thatword

Хотя, возможно, более утомительный ответ, чем вы ищете в настоящее время, я считаю, что хранение таких удобных заметок принесет вам гораздо больше пользы в будущих начинаниях.

keypulse
источник