У меня есть цикл 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.
Ответ будет немного длинным, но я должен рассмотреть немало частей, так что оставайтесь со мной здесь.
Хорошо, давайте начнем с того, что ваш подход к итерации по выводу 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