Як розділити вихід команди на окремі рядки


12
list=`ls -a R*`
echo $list

Всередині скрипту оболонки ця команда echo перелічить усі файли з поточного каталогу, починаючи з R, але в одному рядку. Як я можу надрукувати кожен елемент на одному рядку?

Мені потрібна спільна команда для всіх сценаріїв відбуваються з ls, du, find -type -dі т.д.


6
Загальна примітка: не робіть цього з ls, це порушиться на будь-які дивні назви файлів . Дивіться тут .
тердон

Відповіді:


12

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

echo "$list"

В іншому випадку оболонка розшириться і розділить змінний вміст на пробіли (пробіли, нові рядки, вкладки), і вони будуть втрачені.


15

Замість того, щоб непродумано вводити lsрезультат у змінну, а потім використовувати echoїї, яка видаляє всі кольори, використовуйте

ls -a1

З man ls

       -1     list one file per line.  Avoid '\n' with -q or -b

Я не раджу вам робити що-небудь з результатом ls, крім показу його :)

Наприклад, використовуйте глобуси оболонки та forцикл, щоб зробити щось із файлами ...

shopt -s dotglob                  # to include hidden files*

for i in R/*; do echo "$i"; done

* Це не буде включати в себе поточний каталог .або його батьків , ..хоча


1
Я рекомендую замінити: echo "$i"на printf "%s\n" "$i" (той не задушиться, якщо ім'я файлу починається з "-"). Насправді більшу частину часу echo somethingслід замінити на printf "%s\n" "something".
Олів'є Дулак

2
@OlivierDulac Всі вони починаються Rтут, але загалом, так. Але навіть для сценаріїв спроби завжди розміщувати провідні -шляхи викликають неприємності: люди припускають --, що підтримують лише деякі команди, означає кінець варіантів. Я бачив, find . [tests] -exec [cmd] -- {} \;де [cmd]не підтримується --. Кожен шлях там починається з .будь-якого! Але це не аргумент проти заміни echoна printf '%s\n'. Тут можна замінити весь цикл printf '%s\n' R/*. Bash є printfвбудованим, тому фактично немає обмеження кількості / довжині аргументів.
Елія Каган

12

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

IFS=$'\n' dirs=( $(find . -type d) )

IFS=$'\n'Каже Баш тільки розділити вихід на новий рядок characcters про отримати кожен елемент масиву. Без нього він розділиться на пробіли, тому a file name with spaces.txtбуло б 5 окремих елементів замість одного. Цей підхід порушиться, якщо імена файлів / директорій можуть містити нові рядки ( \n). Це збереже кожен рядок виводу команди як елемент масиву.

Зверніть увагу , що я також змінив старий стиль `command`в $(command)якому кращий синтаксис.

Тепер у вас є масив, який називається $dirs, кожен bof, елементи якого є рядком виводу попередньої команди. Наприклад:

$ find . -type d
.
./olad
./ho
./ha
./ads
./bar
./ga
./da
$ IFS=$'\n' dirs=( $(find . -type d) )
$ for d in "${dirs[@]}"; do
    echo "DIR: $d"
  done
DIR: .
DIR: ./olad
DIR: ./ho
DIR: ./ha
DIR: ./ads
DIR: ./bar
DIR: ./ga
DIR: ./da

Тепер, через деякі дивакові дивацтва (див. Тут ), після цього вам потрібно буде відновити IFSпочаткове значення. Отже, будь-ласка, збережіть його та перевстановіть:

oldIFS="$IFS"
IFS=$'\n' dirs=( $(find . -type d) ) 
IFS="$oldIFS"

Або зробити це вручну:

IFS=" "$'\t\n '

Крім того, просто закрийте поточний термінал. Ваш новий буде знову встановити оригінальний IFS.


Я міг би запропонувати щось подібне, але тоді частина про те, що duвін виглядає, OP зацікавлена ​​у збереженні вихідного формату.
муру

Як я можу зробити цю роботу, якщо імена каталогів містять пробіли?
десерт

@dessert whoops! Тепер він повинен працювати з пробілами (але не з новими рядками). Дякуємо, що вказали на це.
тердон

1
Зауважте, що слід скинути або вимкнути IFS після цього призначення масиву. unix.stackexchange.com/q/264635/70524
муру

@muru oh wow, дякую, я забув про цю дивацтво.
тердон

2

Якщо ви повинні зберегти вихід, і ви хочете масив, mapfileце спрощує.

Спочатку подумайте, чи потрібно взагалі зберігати результати своєї команди. Якщо ви цього не зробите, просто запустіть команду.

Якщо ви вирішили, що хочете прочитати вихід команди як масив рядків, це правда, що один із способів зробити це - відключити глобалізацію, встановити IFSподіл на рядки, використовувати підстановку команд всередині ( )синтаксису створення масиву та скинути IFSзгодом, що складніше, ніж здається. Відповідь Тердона охоплює частину такого підходу. Але я пропоную вам скористатися вбудованою командою mapfileBash shell для читання тексту як масиву рядків. Ось простий випадок, коли ви читаєте з файлу:

mapfile < filename

Це читає рядки в масив, який називається MAPFILE. Без перенаправлення входу ви б читали зі стандартного вводу оболонки (як правило, ваш термінал) замість файлу, який назвав . Щоб прочитати в масив, відмінний від за замовчуванням , введіть його ім'я. Наприклад, це зчитується в масив :< filenamefilenameMAPFILElines

mapfile lines < filename

Ще одна поведінка за замовчуванням, яку ви можете змінити - це те, що символи нового рядка в кінці рядків залишаються на місці; вони відображаються як останній символ у кожному елементі масиву (якщо тільки введення не закінчилось символом нового рядка; у цьому випадку останній елемент не має його). Щоб скопіювати ці нові рядки, щоб вони не з’явились у масиві, передайте цей -tпараметр у mapfile. Наприклад, він читає масив recordsі не записує символи нового рядка в його елементи масиву:

mapfile -t records < filename

Ви також можете використовувати, -tне передаючи ім'я масиву; тобто працює з імпліцитним іменем масиву MAPFILEтакож.

mapfileОболонки вбудованої опори і інші варіанти, і він як альтернатива може бути викликаний в якості readarray. Запустіть help mapfilehelp readarray) для отримання детальної інформації.

Але ви не хочете читати з файлу, ви хочете читати з виводу команди. Для цього використовуйте процес заміщення . Ця команда зчитує рядки з команди some-commandз arguments...аргументами командного рядка та розміщує їх у mapfileмасиві за замовчуванням MAPFILE, при цьому видаляються символи нового рядка:

mapfile -t < <(some-command arguments...)

Заміна процесу замінює фактичне ім'я файлу, з якого може бути прочитаний вихід працює . Файл - це іменована труба, а не звичайний файл, і в Ubuntu він буде назване на зразок (іноді з іншим числом, ніж ), але вам не потрібно дбати про себе за деталями, оскільки оболонка піклується про нього все за лаштунками.<(some-command arguments...)some-command arguments.../dev/fd/6363

Ви можете подумати, що можете відмовитись від заміни, використовуючи натомість, але це не спрацює, тому що, коли у вас конвеєр з декількох команд відокремлений , Bash запускає всі команди в підрозділах . Таким чином, обидва працюють і працюють у власних середовищах , ініціалізованих від оболонки, в якій ви запускаєте конвеєр, але не відокремлені від середовища. У субоболочке , де працює, то масив має отримати населену, але потім цей масив відкидаються , коли кінці команд. ніколи не створюється і не змінюється для абонента.some-command arguments... | mapfile -t|some-command arguments...mapfile -tmapfile -tMAPFILEMAPFILE

Ось як виглядає приклад у відповіді terdon у цілому, якщо ви використовуєте mapfile:

mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
    echo "DIR: $d"
done

Це воно. Вам не потрібно перевіряти, чи IFSвстановлено його , слідкуйте за тим, чи не він був встановлений, і з яким значенням, встановіть його на новий рядок, а потім скиньте або повторно скасуйте його згодом. Вам не потрібно відключити підстановку (наприклад, з set -f) , - які дійсно необхідні , якщо ви хочете використовувати цей метод серйозно, так як імена файлів можуть містити *, ?і [--then повторно включити його ( set +f) згодом.

Ви також можете повністю замінити цей певний цикл однією printfкомандою - хоча це насправді не є користю mapfile, оскільки ви можете це зробити, використовуючи mapfileчи інший метод для заповнення масиву. Ось коротша версія:

mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"

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

Насправді не існує рішення одного розміру.

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

Завдання, показане вище, краще досягти лише однією findкомандою:

find . -type d -printf 'DIR: %p\n'

Ви також можете скористатися зовнішньою командою, якою sedслід додати DIR: до початку кожного рядка. Це, мабуть, дещо некрасиво, і на відміну від команди find це додасть додаткові "префікси" у файли файлів, що містять нові рядки, але воно працює незалежно від його введення, таким чином, воно на зразок відповідає вашим вимогам, і все-таки бажано читати вихід у змінна або масив :

find . -type d | sed 's/^/DIR: /'

Якщо вам потрібно перелічити, а також виконати дію для кожного знайденого каталогу, наприклад, запуск some-commandта проходження шляху до каталогу в якості аргументу, findдозволяє вам зробити це також:

find . -type d -print -exec some-command {} \;

В якості іншого прикладу повернемося до загального завдання - додавання префікса до кожного рядка. Припустимо, я хочу побачити вихід, help mapfileале пронумерувати рядки. Я б фактично не використовував mapfileдля цього, ні будь-який інший метод, який читає його в змінну оболонки або масив оболонок. Припустимо, help mapfile | cat -nщо не дає потрібного форматування, я можу використовувати awk:

help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'

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

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

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