Виконайте дві команди на одному аргументі (без сценарію)


28

Як я можу виконати дві команди на одному вході, не вводячи цей вхід двічі?

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

stat fileName

Команда файлу говорить про тип файлу:

file fileName

Ви можете виконати це в одному рядку таким чином:

stat fileName ; file fileName

Однак вам потрібно ввести fileName двічі.

Як можна виконати обидві команди на одному вході (не вводивши вхід або змінну введення двічі)?

У Linux я знаю, як встановлювати вихідні дані, але як ви працюєте з введеннями?


14
Щоб було зрозуміло, це не "входи", це аргументи (ака-параметри). Введення може здійснюватися через <(введення з файлу вліво) або |(введення з потоку в праву сторону). Є різниця.
goldilocks

6
Не відповідь на ваше запитання, але, можливо, відповідь на вашу проблему: У bash yank-last-argкоманда (ярлик за замовчуванням Meta-.або Meta-_; Metaчасто це ліва Altклавіша) скопіює останній параметр з попереднього командного рядка у поточний. Отже, після виконання stat fileNameви вводите file [Meta-.]і не потрібно вводити це fileNameзнову. Я завжди цим користуюся.
Дубу

2
А різниця в тому , що <і >є Перенаправлення , а НЕ обжигающе . Трубопровід з'єднує два процеси, тоді як перенаправлення просто призначає stdin / stdout.
bsd

@bdowning: Так, але ти переспрямовуєш трубу, чи не так. Ви можете перенаправити початок конвеєра, що читається, з файлу на диску, або ви можете перенаправити кінець конвеєра, який потрібно записати, на файл на диску. Ще трубопроводи. ;-)
Джеймс Хей

Відповіді:


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тієї.


1
!$Один не буде працювати , тому що !$розширюється до останнього слова останньої події історії (так з раніше введених в командному рядку, а не команди Мінстату).
Стефан Шазелас

@ StéphaneChazelas: О, так, моя помилка, я її видалив.
cuonglm

32

Можливо, я змусив мене зграяти за це, ось хитра комбінація розширення баш-дужок і eval, яка, здається, робить трюк

eval {stat,file}" fileName;"


2
розширення брекетів виникло в csh, не bash. bashскопіював його з ksh. Цей код буде працювати у всіх csh, tcsh, ksh, zsh, fish та bash.
Стефан Шазелас

1
Шкода, що ви втрачаєте можливість "вкладки" (автозаповнення) імені файлу через цитату.
Lonniebiz

Вибачте, але я не втримався ні
1616

Вибачте, з чим тут проблема eval? (з посиланням на головні коментарі)
користувач13107

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 до файлу та повідомляти про його атрибути? Імовірно, не просувається вказівник на stdin, тому дозволяє fileнюхати вміст stdin
iruvar

1
@ 1_CR, так. stat -повідомляє statзробити fstat()на своєму fd 0.
Стефан Шазелас

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 статистика також не розпізнає. З zsh та IRIX stat ви можете використовувати stat -f0замість цього.
Stéphane Chazelas

9

Усі ці відповіді мені більшою чи меншою мірою виглядають як сценарії. Для рішення, яке насправді не передбачає сценаріїв, і передбачає, що ви сидите за клавіатурою, набравши в оболонку, ви можете спробувати:

stat somefile Enter
файл Esc_   Enter

У bash, zsh та ksh принаймні в режимах vi та emacs, натискаючи Escape, а потім підкреслення вставляє останнє слово з попереднього рядка в буфер редагування для поточного рядка.

Або ви можете використовувати заміну історії:

stat somefile Enter
файл! $Enter

У bash, zsh, csh, tcsh та більшості інших оболонок !$замінюється останнім словом попереднього командного рядка при розборі поточного командного рядка.


які ще варіанти доступні в zsh / bash, схожі Esc _? Чи можете ви зв’язатись із офіційною документацією?
Абхієет Растогі


1
zsh: linux.die.net/man/1/zshzle та шукати "вставити-останнє слово"
godlygeek

1
@shadyabhi ^ Посилання для стандартних клавішних файлів bash та zsh. Зауважте, що bash просто використовує readline, тому (більшість) bash працюватимуть у будь-якій програмі, яка використовує бібліотеку readline.
godlygeek

3

Як я зрозумів, що робити це розумно, просто використовую xargs, він бере файл / трубу і перетворює його вміст у програмні аргументи. Це може поєднуватися з трійником, який розбиває потік і надсилає його на дві або більше програм. У вашому випадку вам потрібно:

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

На відміну від багатьох інших відповідей, це буде працювати в bash та більшості інших оболонок під Linux. Я б припустив, що це хороший випадок використання змінної, але якщо ви абсолютно не можете використовувати її, це найкращий спосіб зробити це лише за допомогою труб і простих утиліт (якщо припустимо, що у вас немає особливо фантазійної оболонки).

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

EDIT

Як зазначено в коментарях, у цій відповіді є кілька недоліків, перший - потенціал для переплетення вихідних даних. Це можна виправити так:

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

Це змусить команди працювати по черзі, і вихід ніколи не буде переплетеним.

Друге питання полягає у тому, щоб уникнути заміни процесу, яка недоступна в деяких оболонках, цього можна досягти, використовуючи tpipe замість трійника:

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

Це повинно бути портативним для майже будь-якої оболонки (я сподіваюся) і вирішує проблеми в іншій похвалі, однак я пишу цей останній з пам'яті, оскільки в моїй теперішній системі немає доступного tpipe.


1
Заміна процесу - це функція ksh, яка працює лише у ksh(не у варіантах загального користування) bashта zsh. Зверніть увагу , що statі fileбуде працювати одночасно, тому , можливо , їх вихід буде переплетений (я припускаю , що ви використовуєте | catдля виведення сили буферизації межі , що ефект?).
Стефан Шазелас

@ StéphaneChazelas Ви вірно відноситесь до кота, при його використанні мені не вдалося створити жодного випадку переплетених даних при його використанні. Однак я спробую щось на мить створити більш портативне рішення.
Vality

3

Ця відповідь схожа на пару інших:

stat filename   & & file! #: 1

На відміну від інших відповідей, які автоматично повторюють останню аргумент з попередньої команди, цей вимагає від вас рахувати:

ls -ld ім'я файлу   && 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 ім'я файлу

в якій sh призначено $0. Хоча набирати на три символи більше, ця (друга) форма є кращою, оскільки $0це ім’я, яке ви даєте цьому вкладеному сценарію. Він використовується, наприклад, в повідомленнях про помилки в оболонці для цього сценарію, наприклад , коли fileабо statне може бути знайдений або не може бути виконаний з будь-якої причини.


Якщо ви хочете зробити

stat filename 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, труба тут знову не є актуальною, оскільки вхід не повинен змінюватися жодним інструментом, коли він потрапляє на другий.


1

Це має працювати для всіх імен файлів у поточному каталозі.

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *

1
Припустимо, що імена файлів не містять подвійної цитати, зворотної косої риси, долара, зворотного відбору, }... символів і не починаються з -. Деякі printfреалізації (zsh, bash, ksh93) мають a %q. З zsh, це могло б бути:printf 'stat %q; file %1$q\n' ./* | zsh
Стефан Шазелас

@StephaneChezales - все це виправлено зараз, за ​​винятком можливості жорсткого цитата. Я міг би вирішити це з однією, sed s///я думаю, але це стосується сфери застосування - і якщо люди вказують це у своїх іменах, вони повинні використовувати Windows.
mikeserv

@ StéphaneChazelas - ніколи не забував - я забув, як легко з ними впоратися.
mikeserv

Строго кажучи, це не відповідь на поставлене запитання: «Як можна виконати обидві команди на одному вході ( не набираючи вхід… двічі )?» (Наголос додано). Ваша відповідь стосується всіх файлів у поточному каталозі - і вона включає *двічі . Ви також можете сказати stat -- *; file -- *.
Скотт

@Scott - вхід не вводиться двічі - *розширюється. Розширення на *плюс дві команди для кожного аргументу в * це вхід. Побачити? printf commands\ %s\;shift *
mikeserv

1

Тут є кілька різних посилань на "введення", тому я наведу кілька сценаріїв, розуміючи це спочатку. Для вашої швидкої відповіді на питання в найкоротшій формі :

stat testfile < <($1)> outputfile

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

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

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

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 до (а ось де це хитро!) Проклеїти "це слово" у кожному з цих результатів і виконувати лише попередній printf (червоним кольором), якщо він насправді знайшов файл із цим словом. Він також запише результати grep в новий текстовий файл, file_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 - нічого особливого. Цього разу додайте щось для того, щоб перехреститися:

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

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

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