A resposta vai ser um pouco longa, mas eu tenho que abordar algumas partes, então fique comigo aqui.
Bem, vamos começar com o fato de que sua abordagem para iterar a saída grep
não está correta. A abordagem for i in $(); do ...done
rompe com linhas que contêm espaços iniciais (devido à divisão de palavras ) e geralmente não é recomendado (consulte < a href="http://mywiki.wooledge.org/DontReadLinesWithFor"> isto ).
No entanto, o que normalmente é feito é command | while IFS=<word separator> read -r variable; do...done
(que, como bônus adicional, é portável entre os shells parecidos com Bourne). Além disso, vejo que você está usando cut -d : -f 1
para obter o primeiro item da lista separada por dois pontos em cada linha. Podemos usar IFS=":"
para nos livrarmos de cut
part. Seu comando original é assim transformado assim:
grep 'dook' /etc/trueuserowners | while IFS=":" read -r first_word everything_else
do
du -sh /home/$i
done | sort -nr
A variável first_word
obviamente conterá apenas o primeiro campo, e o everything_else
não utilizado conterá ... todo o resto.
Note que também citei 'dook'
part; embora grep
entenda o primeiro item. Embora grep
entenda o primeiro item não-opcional como PATTERN, é uma boa prática proteger o padrão com aspas simples, porque se você usar *
ou alguns outros padrões de regex usados pelo shell, isso ocorrerá sem intenção expansão por bash
e tentará ler arquivos em seu diretório de trabalho atual. Veja este para um exemplo detalhado.
Em seguida, vamos abordar as matrizes. Certamente, podemos adicionar novo item aos arrays com o loop while
mostrado acima e +=
operator:
$ declare -a my_array
$ while IFS=":" read -r name null; do my_array+=("$name"); done < /etc/passwd
$ echo "${my_array[0]}"
root
Isso pode ser conveniente se você quiser processar os itens que você está extraindo mais tarde. No entanto, considerando o fato de que você tem pipe lá, as variáveis desaparecem uma vez subshell em que while
é executado sai. Veja minha pergunta antiga sobre isso.
O que eu recomendo é processar tudo no while loop
e usar du -sb
para precisão nos cálculos. Assim, você poderia fazer:
# 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 }' )
# output the usage for current user
printf "%s\t/home/%s\n" "$user_usage" "$first_word"
total=$(($total+$user_usage))
done
Observe como usei || { echo "$total"; break;}
. Uma vez que não há nada para ler a partir de stdin
(que neste caso vem do pipe), read
retorna o status de saída 1, então quando read
retorna 1 sabemos que é feito leitura e processamento, e podemos produzir o total uso que calculamos.
Quanto à produção de dados legíveis, poderíamos usar numfmt
ou alguns outros utilitários . Algo como numfmt --to=iec-i --suffix=B $user_usage
seria suficiente.
No geral, isso pode ser usado como uma linha, se cortarmos os nomes das variáveis para algo curto, mas não há nenhuma vantagem em ter uma linha única. Basta fazer as coisas da maneira mais correta possível e não se preocupe com o tamanho do código.
Juntando tudo, a solução completa deve ser:
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 }' )
# 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