Дилемма "Пока-петля" в Bash
Я хочу, чтобы вычислить все файлы * bin внутри данного каталога. Первоначально я работал с for-loop
:
var=0
for i in *ls *bin
do
perform computations on $i ....
var+=1
done
echo $var
Однако в некоторых каталогах слишком много файлов, что приводит к ошибке: Argument list too long
Поэтому я пытался это с каналом while-loop
:
var=0
ls *.bin | while read i;
do
perform computations on $i
var+=1
done
echo $var
Проблема сейчас в том, что с помощью труб создаются подоболочки. Таким образом, echo $var
возвращается 0
,
Как я могу справиться с этой проблемой?
Оригинальный код:
#!/bin/bash
function entropyImpl {
if [[ -n "$1" ]]
then
if [[ -e "$1" ]]
then
echo "scale = 4; $(gzip -c ${1} | wc -c) / $(cat ${1} | wc -c)" | bc
else
echo "file ($1) not found"
fi
else
datafile="$(mktemp entropy.XXXXX)"
cat - > "$datafile"
entropy "$datafile"
rm "$datafile"
fi
return 1
}
declare acc_entropy=0
declare count=0
ls *.bin | while read i ;
do
echo "Computing $i" | tee -a entropy.txt
curr_entropy=`entropyImpl $i`
curr_entropy=`echo $curr_entropy | bc`
echo -e "\tEntropy: $curr_entropy" | tee -a entropy.txt
acc_entropy=`echo $acc_entropy + $curr_entropy | bc`
let count+=1
done
echo "Out of function: $count | $acc_entropy"
acc_entropy=`echo "scale=4; $acc_entropy / $count" | bc`
echo -e "===================================================\n" | tee -a entropy.txt
echo -e "Accumulated Entropy:\t$acc_entropy ($count files processed)\n" | tee -a entropy.txt
3 ответа
Проблема в том, что цикл while выполняется в подоболочке. После завершения цикла while копия subshell var
отбрасывается, а оригинал var
родителя (чье значение не изменилось) отражается.
Один из способов исправить это - использовать процесс подстановки, как показано ниже:
var=0
while read i;
do
# perform computations on $i
((var++))
done < <(find . -type f -name "*.bin" -maxdepth 1)
Посмотрите на BashFAQ / 024 для других обходных путей.
Обратите внимание, что я также заменил ls
с find
потому что разбирать нехорошоls
,
POSIX-совместимым решением было бы использование канала (p-файл). Это решение очень хорошее, портативное и POSIX, но записывает что-то на жесткий диск.
mkfifo mypipe
find . -type f -name "*.bin" -maxdepth 1 > mypipe &
while read line
do
# action
done < mypipe
rm mypipe
Ваша труба - это файл на вашем жестком диске. Если вы хотите избежать ненужных файлов, не забудьте удалить их.
Итак, исследуем общую проблему, передав переменные из цикла while с частичной оболочкой в родительский. Одно решение, которое я нашел, здесь отсутствует, заключалось в использовании строки здесь. Поскольку это был bash-ish, и я предпочел решение POSIX, я обнаружил, что строка здесь - это просто ярлык для документа здесь. Обладая этими знаниями, я придумал следующее, избегая подоболочки; тем самым позволяя устанавливать переменные в цикле.
#!/bin/sh
set -eu
passwd="username,password,uid,gid
root,admin,0,0
john,appleseed,1,1
jane,doe,2,2"
main()
{
while IFS="," read -r _user _pass _uid _gid; do
if [ "${_user}" = "${1:-}" ]; then
password="${_pass}"
fi
done <<-EOT
${passwd}
EOT
if [ -z "${password:-}" ]; then
echo "No password found."
exit 1
fi
echo "The password is '${password}'."
}
main "${@}"
exit 0
Одно важное замечание для всех копировальных программ - это то, что здесь-документ настраивается с использованием дефиса, указывающего на то, что вкладки следует игнорировать. Это нужно, чтобы макет оставался приятным. Это важно отметить, потому что stackru не отображает табуляции в "коде" и заменяет их пробелами. Grmbl. Итак, не искажайте мой код, просто потому, что вы, ребята, предпочитаете пробелы, а не вкладки, в данном случае это не имеет значения!
Это, вероятно, не работает в разных редакторах (настройках), а что нет. Таким образом, альтернативой было бы иметь это как:
done <<-EOT
${passwd}
EOT
Это также можно сделать с помощью цикла for:
var=0;
for file in `find . -type f -name "*.bin" -maxdepth 1`; do
# perform computations on "$i"
((var++))
done
echo $var