Що робить $ (ls * .txt)?


5

У терміналі unix, якщо я роблю:

egrep "Stuff" $(ls *.txt)

Результат очевидний. Але що робить

$(ls *.txt)робити самостійно? Я бачу, що можу повторити ефект

egrep "Stuff" *.txt

То що таке $(ls *.txt)?





Ви зробите це лише в тому випадку, якщо пишете прихований скрипт оболонки, який підтримує отримання додаткових grepопцій з імен файлів, які закінчуються .txt. Можливо, завдяки IFS=$'\n'цьому ви зможете уникнути розбиття назви слів з пробілами, відмінними від нового рядка. Очевидно, це не те, що хто хоче у звичайних обставинах; Я просто намагаюся уявити фактичний випадок використання для того, що зазвичай є поганим / неправильним способом зробити щось.
Пітер Кордес

Забавний факт: у GNU lsє--quoting-style=shell-always варіант, який може зробити це безпечним (але все ж гірше, ніж просто *.txt). Arch Linux будується lsз цитуванням, включеним за замовчуванням, коли вихід є терміналом, я думаю, для копіювання / вставки.
Пітер Кордес

Відповіді:


28

"що робить $(ls *.txt)"

Коротка відповідь

$(ls *.txt)збирає список імен файлів, а потім обробляє їх . Ви НЕ використовувати це.

Більш довга відповідь

  • lsпризначений для людського результату. Як писав один із супровідників Ls у відповідь на те, чому lsб не запропонувати --nullваріант:

    Якби ми це робили, то це був би інтерфейс, який ми використовуємо. Однак lsце справді інструмент прямого споживання людиною , і в цьому випадку подальша обробка є менш корисною. Для подальшої обробки findбільше підходить (1). [наголос додано]

    Іншими словами, використання lsдля чогось іншого, крім відображення для людей, просто не підтримується. lsНаприклад, технічні працівники, наприклад, нещодавно змінили вихідний формат за замовчуванням на те, що вони хоч і зручніші для людини без попередження . Отже, дотримуйтесь їхніх порад: якщо ви збираєтеся робити подальшу обробку імен файлів, використовуйте findзамість ls.

  • $(...)- це підміна команд. Одним із наслідків використання підстановки команд є те, що вилучені символи нового рядка видаляються. Якщо прізвище останнього файлу у списку містить кінцеві нові рядки, вони будуть видалені.

  • Оскільки текст $(...)не є подвійним цитуванням, текст, який він видає, буде підпорядкований:

    • Розбиття слів

    • Розширення шляху

    Результатом обох є подальше керування назвами файлів.

  • не кажучи вже про питання з іменами файлів , починаючи з -з - за якого бракує --(для обох lsі , egrepяк в Ubuntu egrepє реалізація GNU приймає параметри навіть після аргументів , не варіант , якщо POSIXLY_CORRECTне в навколишньому середовищі).

  • також, якщо будь-який з цих txtфайлів мав типовий каталог , lsвін перелічив би їхній вміст замість себе.

  • і якщо txtфайлів немає , вихідний файл lsбуде порожнім (хоча ви побачите помилку щодо відсутнього *.txtфайлу) і оскільки egrepне отримаєте аргументу файлу, він шукатиме речі у своєму стандартному вході (і, здавалося б, висить).

Приклад

Створимо 4 файли, що містять Stuffу нашому каталозі:

$ echo Stuff | tee file1 file2 'a b c.txt' 'f* .txt'
Stuff
$ ls -Q
"a b c.txt"  "file1"  "file2"  "f* .txt"

Тепер запустімо команду egrep:

$ egrep "Stuff" $(ls *.txt)
grep: a: No such file or directory
grep: b: No such file or directory
grep: c.txt: No such file or directory
file1:Stuff
file2:Stuff
f* .txt:Stuff
grep: .txt: No such file or directory

Зауважте, що ми отримуємо 4 повідомлення про помилки щодо неіснуючих файлів. Це пов’язано з розщепленням слів . Результат також показує збіги з двома файлами, file1і file2їх не слід було шукати, оскільки вони не закінчуються .txt. Це через розширення _pathname`.

Правильно написана команда створює два успішні збіги і жодних помилок:

$ egrep -- "Stuff" *.txt
a b c.txt:Stuff
f* .txt:Stuff

Рекомендоване рішення

Використання:

egrep -- "Stuff" *.txt

або POSIXly:

grep -E -- "Stuff" *.txt

або:

grep -E -e Stuff -- *.txt

Це буде працювати з будь-яким ім'ям файлу і не має жодного з обмежень lsпідходу.


1
досить чіткий підхід
solfish

1
Ваш перший пункт щодо зміни назв файлів вірний лише тоді, коли вихід надходить до терміналу, а не тоді, коли він переходить до труби, як у цьому випадку.
Стефан Шазелас

@ StéphaneChazelas Цікаво. Я також це бачу. Дякую. Я щойно оновив відповідь, щоб замінити цю заяву цитатою одного з супроводжувачів, заявивши, що lsїї не слід використовувати ні для чого, крім споживання людиною.
John1024

6

$( )Виконує команду ls *.txtі повертає STDOUTкоманду.

Яке саме це використання - це помилка програмування новачка щонайменше на трьох рівнях:

  1. egrep 'Stuff' *.txt працює, як ви сказали, за винятком файлів, названих чимось на кшталт A File.txt
  2. Використовувати lsвихід для введення програми нерозумно. Дивіться причини
  3. $( ls *.txt)неправильні файли з пробілами та іншими смішними персонажами. find . -maxdepth 1 -type f -iname '*.txt' -print0 | xargs -0 egrep 'Stuff'є більш стійким до куль.

Надуманий приклад розширення / плутанини глобальної оболонки у відповідь на @ 8bittree:

$ /bin/ls -l -b
total 36
-rw------- 1 w3 walt 2 Aug 11 13:41 A\ \\012\ File.txt
-rw------- 1 w3 walt 2 Aug 11 13:42 A\ \n\ File.txt
-rw------- 1 w3 walt 2 Aug 11 13:40 A\ File.txt

$ grep "STUFF" $(ls *.txt)
grep: A: No such file or directory
grep: \012: No such file or directory
grep: File.txt: No such file or directory
grep: A: No such file or directory
grep: File.txt: No such file or directory
grep: A: No such file or directory
grep: File.txt: No such file or directory

$ find . -maxdepth 1 -type f -iname '*.txt' -print0 | xargs -0 egrep 'Stuff'

$ find . -maxdepth 1 -type f -iname '*.txt' -print0 | xargs -0 egrep '1'
./A File.txt:1

Крім того, використання find's -execдій, а не -print0+, xargs -0було б більш ефективним, хоча я визнаю, що його синтаксис викликає занепокоєння. У цьому випадку це було б find [...] -exec egrep 'Stuff' {} \;або find [...] -exec egrep 'Stuff' {} +; у другому випадку grepобробляється декілька файлів одночасно, тому результат буде префіксований файловим шляхом (який можна відключити прапором grep' -h)
Аарон

@Aaron Я ніколи не бачив синтаксису знахідки "{} +", дуже цікавого. Використання "-exec egrep 'Stuff' {} \;" без + насправді був би менш ефективним, оскільки він би відключив новий процес grep для кожного файлу. Крім того, я зазвичай запускаю grep з додатковим файлом / dev / null, тому я отримаю імена файлів, перелічені у висновку, навіть якщо в команді grep закінчується лише 1 файл. наприклад знайти ... -print0 | xargs -0 egrep 'Stuff' / dev / null
Aner

@ Aner щодо ефективності {} \;ви праві. Крім того , grep«s -Hпрапор коротше , ніж тип /dev/null;) Завжди добре перечитати командування --help(якщо не його людина) , час від часу, такі ласощі приходять в існування кожні кілька версій.
Аарон

1
find -print0 | xargsне є більш надійним, ніж grep -E *txt. Глобуси абсолютно безпечні при будь-якому імені файлу, яке ви кидаєте на них. Ваш findприклад також спрацює, так, але це не має жодної користі над земною кулею; це просто набагато складніше.
terdon

1
Це не приклад того, що глобус плутається з пробілами, це лише поганий приклад розбору ls. Використовувати глобуси означає використовувати глобальний візерунок, а не результати роботи lsна цьому шаблоні. Як зазначав 8bittree, grep STUFF *txtбуде добре працювати, як і буде for i in *txt; do grep STUFF "$i"; done. Немає імені файлу, який би змусив глобального задуху. Ось для чого вони розроблені.
terdon

3

Нічого собі, на хвилину я подумав, що ти цікавишся `$(ls *.txt)`(із $(..)плюс зворотами)

Оскільки на питання вже відповіли, я просто попередитиму вас про одне. Ось послідовність команд, щоб показати вам, чому це може бути небезпечно:

  • set -x
    Щоб побачити, що виконується
  • touch 'rm -f *.txt'
    Створіть текстовий файл під назвою 'rm -f * .txt', я тут використовую прості лапки, щоб Bash не розширював підстановку та пробіли.
  • `$(ls *.txt)`
    Ось хитра частина. Команда ls *.txtбуде виконана, а потім перенаправить результат на STDOUT. Цей результат rm -f *.txtтепер буде виконуватися через зворотні котирування.
    Отже, rm видалить усі текстові файли, у нашому випадку - файлrm -f *.txt

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


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

1
Так, ти маєш рацію, назва змінилася! У будь-якому випадку, я думаю, що це не зашкодить нікому зрозуміти, що може зробити подвійне виконання команд.
Sayardiss

2
$( ... )

Це заміна команд, вона призначає вихід однієї або декількох команд як вхід до іншої команди. Старий синтаксис: ` ... `.

Різне в тому, що ваші команди запускаються в різних оболонках:

echo $BASHPID $( echo $BASHPID )
5718 7138

Він фактично викликає підзарядку:

echo $BASH_SUBSHELL $( echo $BASH_SUBSHELL )
0 1

0

Це питання, мабуть, мало корисного в реальному світі, але,

Кращий спосіб отримати корисний результат:

echo $(ls *.txt)

Або просто

ls *.txt

У цьому списку будуть всі файли з розширенням .txt. Щоб знайти текстові файли, що містять певний рядок,

egrep -l "Stuff" *.txt

Для отримання додаткової інформації про інструменти пошуку зробіть:

man egrep

2
Як echo $(ls *.txt)більш придатний до вживання , що говорять echo *.txt. Це, безумовно, набагато менш надійно, оскільки більшість пунктів, зазначених у відповіді Джона, також стосуються там. Зверніть увагу, що ls *.txtне перераховано всі файли з розширенням .txt . Вам для цього знадобиться ls -d -- *.txt.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.