Захват содержимого цикла в переменную

У меня есть цикл for, который выводит все из /etc/trueuserowners в du. Я пытаюсь выяснить, как получить выходные данные цикла for и поместить ВСЕ его в переменную, не помещая его в массив.

for i in $(grep dook /etc/trueuserowners | cut -d : -f 1); do du -sh /home/$i; done | sort -nr

Основная цель этого состоит в том, чтобы я мог взять вывод du -sk, поместить его в переменную p и взять p, чтобы вычислить читаемые человеком формы и получить общее значение при самый конец.

Я пытаюсь свести это к одной строке, но могу / преобразую в сценарий bash, если мне АБСОЛЮТНО придется.

Это выполняется на сервере с пользователями cPanel.

Содержание /etc/trueuserowners выглядит следующим образом.

user1: reseller
user2: reseller
user3: reseller

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

for i in $(grep root /etc/trueuserowners | cut -d : -f 1); do du -sh /home/$i; done | sort -nr
992K    /home/demoabusecenter
820K    /home/lakshdljkashdlkj
151M    /home/hyg8
58M /home/bugsabusecenter
21G /home/esportsoverlay
3.3G    /home/yourabuse
2.8M    /home/kf30ls08f2k0
1.4M    /home/perm2term
1.2M    /home/justcheck
1.1M    /home/myinfo1

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

Я думаю, что у меня есть решение для этого, но я не могу понять, как получить этот вывод в переменную.

Вот что происходит, когда я пытаюсь.

for i in $(grep root /etc/trueuserowners | cut -d : -f 1); do p=$(du -sh /home/$i); done | sort -nr | echo $p
20981044 /home/esportsoverlay

По какой-то причине он преобразует вывод для пространства в МБ. Что хорошо, потому что я могу преобразовать это в ГБ. Я просто не могу заставить его отображать все. Он показывает только последний вывод пользователя из цикла for.

2
задан 2 December 2017 в 01:49

1 ответ

Ответ будет немного длинным, но я должен рассмотреть немало частей, так что оставайтесь со мной здесь.

Хорошо, давайте начнем с того, что ваш подход к итерации по выводу grep не совсем верен. Подход for i in $(); do ...done разрывается со строками, которые содержат начальные пробелы (из-за разбиения слов ), и, как правило, не рекомендуется (см. этот ).

Однако, как правило, делается command | while IFS=<word separator> read -r variable; do...done (который в качестве дополнительного бонуса переносим между Bourne-подобными оболочками). Также я вижу, что вы используете cut -d : -f 1 для получения первого элемента из списка, разделенного двоеточиями, в каждой строке. Затем мы можем использовать IFS=":", чтобы избавиться от части cut. Таким образом, ваша исходная команда преобразуется следующим образом:

grep 'dook' /etc/trueuserowners | while IFS=":" read -r first_word everything_else 
do 
    du -sh /home/$i
done | sort -nr

Переменная first_word, очевидно, будет содержать только первое поле, а неиспользуемая everything_else будет содержать ... все остальное.

Обратите внимание, что я также процитировал 'dook' часть; хотя grep понимает первый пункт. Хотя grep понимает первый элемент, не являющийся опцией, как PATTERN, на самом деле рекомендуется защищать шаблон с помощью одинарных кавычек, потому что, если вы используете * или некоторые другие шаблоны регулярных выражений, которые используются оболочкой, которые непреднамеренно будут выполнять имя файла расширение на bash и попытается прочитать файлы в вашем текущем рабочем каталоге. См. этот для подробного примера.

Далее, давайте обратимся к массивам. Мы, безусловно, можем добавить новый элемент в массивы с помощью цикла while, который я показал выше, и оператора +=:

$ declare -a my_array
$ while IFS=":" read -r name null; do my_array+=("$name"); done < /etc/passwd
$ echo "${my_array[0]}"
root

Это может быть удобно, если вы хотите обработать элементы Вы извлекаете позже. Однако, учитывая тот факт, что у вас есть pipe, переменные исчезают, когда завершается подоболочка, в которой запускается while. См. Мой старый вопрос об этом.

Я бы порекомендовал обработать все в while loop и использовать du -sb для точности вычислений. Таким образом, вы можете сделать:

# read what grep finds line by line and print total once there's no more lines from grep
grep 'dook' /etc/trueuserowners | while IFS=":" read -r first_word everything_else || { echo "$total"; break;} 
do 
    user_usage=$( du -sb /home/"$first_word" | awk '{print $1}' )
    # output the usage for current user
    printf "%s\t/home/%s\n" "$user_usage"  "$first_word"
    total=$(($total+$user_usage))
done 

Обратите внимание, как я использовал || { echo "$total"; break;}. Если нечего читать из stdin (который в данном случае исходит из канала), команда read возвращает состояние выхода 1, поэтому, когда read возвращает 1, мы знаем, что чтение и обработка завершены, и мы можем вывести итоговое значение использование мы рассчитали.

Что касается вывода данных, читаемых человеком, мы могли бы использовать numfmt или некоторые другие утилиты . Что-то вроде numfmt --to=iec-i --suffix=B $user_usage будет достаточно.

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

Собрав все вместе, полное решение должно быть:

grep 'dook' /etc/trueuserowners | while IFS=":" read -r username trash || { printf "%s\ttotal\n" $( numfmt --to=iec-i --suffix=B "$total"); break;} 
do 
    # get usage in bytes
    user_usage=$( du -sb /home/"$username" | awk '{print $1}' )
    # get human-readable
    usage_human_readable=$( numfmt --to=iec-i --suffix=B "$user_usage" )
    # output the usage for current user
    printf "%s\t/home/%s\n" "$usage_human_readable"  "$username"
    total=$(($total+$user_usage))
done 
1
ответ дан 2 December 2017 в 01:49

Другие вопросы по тегам:

Похожие вопросы: