Дилемма "Пока-петля" в 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
Другие вопросы по тегам