Дилемма подголовника в замкнутом цикле в 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
Ответы
Ответ 1
Проблема в том, что цикл while выполняется в подоболочке. После завершения цикла while копия подголовника 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
, потому что это не хорошая практика parse ls
.
Ответ 2
Решение, совместимое с POSIX, должно состоять в использовании файла pipe (p file). Это решение очень красивое, портативное и POSIX, но что-то пишет на жестком диске.
mkfifo mypipe
find . -type f -name "*.bin" -maxdepth 1 > mypipe &
while read line
do
# action
done < mypipe
rm mypipe
Ваша труба - это файл на вашем жестком диске. Если вы хотите избежать ненужных файлов, не забудьте удалить его.
Ответ 3
Таким образом, исследуя общую проблему, передавая переменные из цикла подзаголовка в родительский цикл. Единственное решение, которое я нашел, здесь отсутствовало, это использовать здесь-строку. Поскольку это был 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
Одно важное замечание для всех пастеров копирования заключается в том, что документ здесь настроен с использованием дефиса, указывающего, что вкладки следует игнорировать. Это необходимо, чтобы макет был приятным. Важно отметить, что stackoverflow не отображает вкладки в "коде" и заменяет их пробелами. Grmbl. Так что, не портите мой код, просто потому, что вы, ребята, предпочитаете пробелы перед вкладками, это не имеет значения в этом случае!
Это, вероятно, ломается на другом редакторе (настройках), а что нет. Таким образом, альтернативой было бы иметь это как:
done <<-EOT
${passwd}
EOT
Ответ 4
Это также можно сделать с помощью цикла for:
var=0;
for file in `find . -type f -name "*.bin" -maxdepth 1`; do
# perform computations on "$i"
((var++))
done
echo $var