Читання виводу команди в масив в Bash


111

Мені потрібно прочитати висновок команди з мого сценарію в масив. Команда, наприклад:

ps aux | grep | grep | x 

і він дає вихідний рядок за рядком так:

10
20
30

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


5
Привіт @barp, ВІДПОВІДЬ ВАШИ ЗАПИТАННЯ, щоб ваш тип не був стоком для всієї громади.
Джеймс

9
@James не в тому, що він не відповідає на його запитання ... це питання Q / A. Він просто не позначив їх як відповіді. Він повинен їх позначити. Підказка. @ barp
DDPWNAGE

4
@Barp, позначте питання як відповідь.
smonff

Пов'язане: Прокручування вмісту файлу в Bash з моменту зчитування виводу команди за допомогою процесу заміни аналогічно читанню з файлу.
codeforester

Відповіді:


161

Інші відповіді зламається , якщо висновок команди містить прогалини (які досить часто) або кульку символи , такі як *, ?, [...].

Для отримання результату команди в масиві, що містить один рядок на елемент, є по суті 3 способи:

  1. Використання Bash≥4 mapfile- це найбільш ефективно:

    mapfile -t my_array < <( my_command )
  2. В іншому випадку цикл зчитує вихід (повільніше, але безпечно):

    my_array=()
    while IFS= read -r line; do
        my_array+=( "$line" )
    done < <( my_command )
  3. Як запропонував Чарльз Даффі у коментарях (спасибі!), Наступне може бути кращим, ніж метод циклу у №2:

    IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )

    Будь ласка, переконайтеся, що ви використовуєте саме цю форму, тобто переконайтеся, що у вас є:

    • IFS=$'\n' у тому ж рядку, що й readвираз: це встановить змінну середовища лише IFS для readоператора. Так що це взагалі не вплине на решту вашого сценарію. Мета цієї змінної полягає в тому, щоб сказати, readщоб перервати потік на символі EOL \n.
    • -r: це важливо. Це говорить read про те, що не слід інтерпретувати косої риси як послідовності втечі.
    • -d '': зверніть увагу на пробіл між -dопцією та її аргументом ''. Якщо ви не залишите пробіл тут, запит ''ніколи не буде помічений, оскільки він зникне на кроці видалення цитат, коли Bash розбере заяву. Це говорить readпро припинення читання в нульовому байті. Деякі люди пишуть це як -d $'\0', але насправді це не потрібно. -d ''краще.
    • -a my_arrayвказує readзаповнити масив my_arrayпід час читання потоку.
    • Ви повинні використовувати printf '\0'оператор після my_command , щоб readповернутись 0; насправді це не велика справа, якщо ви цього не зробите (ви просто отримаєте код повернення. 1Це нормально, якщо ви не використовуєте set -e- чого ви все одно не повинні), але просто пам’ятайте про це. Це чистіше і більш семантично правильно. Зауважте, що це відрізняється від того printf '', що нічого не видає. printf '\0'друкує нульовий байт, необхідний для того, readщоб радісно перестати читати там (пам'ятаєте -d ''варіант?).

Якщо ви можете, тобто якщо ви впевнені, що ваш код буде працювати на Bash≥4, скористайтеся першим методом. І ви можете бачити, що це теж коротше.

Якщо ви хочете використовувати read, цикл (метод 2) може мати перевагу перед методом 3, якщо ви хочете виконати деяку обробку, коли рядки читаються: у вас є прямий доступ до нього (через $lineзмінну в прикладі, який я дав), і Ви також маєте доступ до вже прочитаних рядків (через масив ${my_array[@]}у прикладі, який я дав).

Зауважте, що mapfileпередбачено спосіб зворотного виклику eval'd на кожному прочитаному рядку, і насправді ви можете навіть сказати йому, щоб викликати цей зворотний дзвінок лише через кожну N прочитаних рядків; перегляньте help mapfileі варіанти, -Cі -cтам. (Моя думка з цього приводу полягає в тому, що це трохи незграбно, але його можна використовувати іноді, якщо у вас є лише прості речі - я не дуже розумію, чому це було здійснено в першу чергу!).


Тепер я розповім, чому такий метод:

my_array=( $( my_command) )

порушується, коли є пробіли:

$ # I'm using this command to test:
$ echo "one two"; echo "three four"
one two
three four
$ # Now I'm going to use the broken method:
$ my_array=( $( echo "one two"; echo "three four" ) )
$ declare -p my_array
declare -a my_array='([0]="one" [1]="two" [2]="three" [3]="four")'
$ # As you can see, the fields are not the lines
$
$ # Now look at the correct method:
$ mapfile -t my_array < <(echo "one two"; echo "three four")
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # Good!

Тоді деякі люди рекомендуватимуть IFS=$'\n'це виправити:

$ IFS=$'\n'
$ my_array=( $(echo "one two"; echo "three four") )
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # It works!

Але тепер давайте скористаємося ще однією командою з глобусами :

$ echo "* one two"; echo "[three four]"
* one two
[three four]
$ IFS=$'\n'
$ my_array=( $(echo "* one two"; echo "[three four]") )
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="t")'
$ # What?

Це тому, що у мене є файл, який називається tу поточному каталозі ... і це ім'я файлу відповідає глобулю [three four] ... У цей момент деякі люди рекомендують використовувати set -fдля відключення глобалізації: але подивіться на це: ви повинні змінити IFSі використовувати, set -fщоб мати можливість виправити виправлення зламана техніка (а ти навіть це навіть не фіксуєш)! при цьому ми дійсно боремося з оболонкою, не працюючи з оболонкою .

$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'

тут ми працюємо з оболонкою!


4
Це чудово, про що я ніколи не чув mapfile, це саме те, чого я пропускав роками. Я думаю, що в останніх версіях bashє стільки приємних нових функцій, я повинен просто провести кілька днів, читаючи документи і записуючи приємний чіт-лист.
Гена Павловського

6
Btw, щоб використовувати цей синтаксис < <(command)у скриптах оболонки, рядок shebang має бути #!/bin/bash- якщо запустити як #!/bin/sh, bash вийде із синтаксичною помилкою.
Гена Павловського

1
Розширюючись на корисну примітку @ GenePavlovsky, сценарій також повинен бути виконаний з командою bash, bash my_script.shа не командою shsh my_script.sh
Vito

2
@Vito: справді ця відповідь стосується лише Bash, але це не повинно бути проблемою, оскільки строго сумісні оболонки POSIX навіть не реалізують масиви ( shі dashвзагалі не знають про масиви, за винятком, звичайно, для $@масив позиційних параметрів ).
gniourf_gniourf

3
Розглянемо ще одну альтернативу, яка не потребує bash 4.0 IFS=$'\n' read -r -d '' -a my_array < <(my_command && printf '\0')- вона обидво працює коректно в bash 3.x, а також проходить через невдалий статус виходу з my_commandдо read.
Чарльз Даффі

86

Можна використовувати

my_array=( $(<command>) )

для збереження виводу команди <command>в масивmy_array .

Ви можете отримати доступ до довжини цього масиву, використовуючи

my_array_length=${#my_array[@]}

Тепер довжина зберігається в my_array_length.


19
Що робити, якщо у висновку $ (команда) є пробіли та кілька рядків з пробілами? Я додав "$ (команда)", і він розміщує весь вихід з усіх рядків у перший [0] елемент масиву.
ikwyl6

3
@ ikwyl6 вирішення завдання присвоює команду виводу змінній, а потім робить масив з нею або додає її до масиву. VAR="$(<command>)"а потім my_array=("$VAR")абоmy_array+=("$VAR")
Віто

10

Уявіть, що ви збираєтеся помістити файли та імена каталогів (під поточну папку) до масиву та порахувати його елементи. Сценарій був би таким;

my_array=( `ls` )
my_array_length=${#my_array[@]}
echo $my_array_length

Або ви можете повторити цей масив, додавши такий сценарій:

for element in "${my_array[@]}"
do
   echo "${element}"
done

Зауважте, що це основна концепція, і вхід вважається попередньо очищеним, тобто видалення зайвих символів, обробка порожніх рядків тощо (що не входить в тему цієї теми).


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