LINUX.ORG.RU

bash вывод переменной после цикла

 ,


1

1

Не понимаю, почему такое работает

for ((i=0;i<3;i++)); do
    counter=0
    for ((n=0;n<10;n++)); do
        counter=$((counter+1))
    done
    echo $counter
done

10
10
10

А вот такое нет

#!/bin/bash

find -maxdepth 1 -mindepth 1 -type d -print0 | while IFS= read -r -d '' tv_show; do
    let total_bitrate=0
    let total_videos=0
    find "$tv_show" -type f -regextype posix-egrep -regex ".*\.(avi|mkv|mp4)" -print0 | while IFS= read -r -d '' file_path; do
        bitrate=$(ffprobe -v error -show_entries format=bit_rate \
                          -of default=noprint_wrappers=1 "$file_path" \
                      | awk -F= '{print $2}')
        total_bitrate=$((total_bitrate+bitrate))
        total_videos=$((total_videos+1))
        echo $file_path $total_bitrate $total_videos
    done
    echo "$tv_show $total_videos $total_bitrate"
done

Смысл этого костыльного скрипта - посчитать средний битрейт для всех файлов в каждой из директорий. Пока расчет среднего я отложил, потому что в выводе

echo "$tv_show $total_videos $total_bitrate"

после вложенного цикла (по результатам посчета в директории) я по каждой из директорий получаю

./Имя_директории 0 0

Хотя в выводе внутри вложенного цикла

echo $file_path $total_bitrate $total_videos

все данные правильные: битрейт увеличивается, количество файлов тоже. Если дебажить с -x, то перед выводом после вложенного цикла внезапно появляется

+ IFS=
+ read -r -d '' file_path
+ echo './Имя_директории 0 0'
./Имя_директории 0 0

Я, очевидно, где-то накосячил, но в упор не вижу где. Вроде, здесь не нужны никакие дополнительне объявления глобальных переменных, т.к. у меня просто два вложенных цикла, и обе переменны total_videos и total_bitrate видны во всем скрипте. Так почему тогда в выводе после вложенного цикла подсчета по файлам я получаю два ноля?

Пример с выхлопом для одной из директорий (сначала без дебага, потом с дебагом) - https://pastebin.com/56h9ysMW

Как-то так надо перенаправлением обойтись:

while IFS= read -r -d '' tv_show; do
    ...
done < <( find -maxdepth 1 -mindepth 1 -type d -print0 )

Конвеер создаёт subshell, а это отдельный процесс со своей собственной копией переменных и родительская оболочка те значения не получит.

xaizek ★★★★★ ()

Правильный ответ уже дали, но раз уж меня кастанули запощу скрипт в своём стиле:

find -maxdepth 1 -mindepth 1 -type d -print0 | while IFS= read -r -d '' tv_show; do
    find "$tv_show" \
        -type f \
        -regextype posix-egrep \
        -regex ".*\.(avi|mkv|mp4)" \
        -exec ffprobe \
            -v error \
            -show_entries format=bit_rate \
            -of default=noprint_wrappers=1 \
            {} \; \
    | awk -F= '
        {
            bit_rate+=$2;
        }
        END {
            print("'"$tv_show"'",
                bit_rate,
                NR,
                NR?int(bit_rate/NR):"none");
        }'
done
legolegs ★★★★★ ()
Ответ на: комментарий от xaizek

Как-то так надо перенаправлением обойтись:

Но самое удивительное в том, что люди юзают find для того, чтобы получить то, что делает просто '*'.

for d in * .*; do 
  [[ ! -d $d || $d = . || $d = .. ]] && continue 
# остальной скрипт с "$d"
  let total_bitrate=0
  ....
done
И никакого fork-а и субпроцессов.

vodz ★★★★★ ()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от futurama

второй find тоже будешь bash’ем эмулировать?

Да запросто. Найдёт все *.c:

scan_a() {
        local p
        for p in "$1/"* "$1/."* ; do
                [[ -L "$p" ]] && continue
                if [[ -d "$p" ]]; then
                        [[ "${p:0-2:2}" == /. || "${p:0-3:3}" == /.. ]] && continue
                        scan_a "$p"
                elif [[ "${p:0-2:2}" == .c && "${p:0-3:1}" != / && -f "$p" ]]; then
                        convert "$p"
                fi
        done
}
Иногда так и надо, когда сложное условие поиска и невозможно запрограммировать встроенной логикой в find

А если первый find надо усложнить?

Вот именно, тогда find может и не справиться.

vodz ★★★★★ ()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от vodz

Я не зря find начал использовать. У меня структура директорий сложная, т.е. в первом цикле при итерации директорий я бы сразу файлы не получил. Вложенность разная, есть больше 3.

Можно было вообще на питоне написать это дело, но я не нашел вменяемых либ для работы с медиафайлами.

PawsOnFire ()
Ответ на: комментарий от DELIRIUM

find используют, чтобы такой ошибки не было.

find используют для рекурсивного поиска имен файлов по условиям. Если все найденные файлы поместить в массив, то при превышении ARG_MAX для вызова программ получите ровно ту же ошибку. В большинстве задач, как и в этом топике работа происходит с каждым найденным файлом в отдельности. Более того, скриптом даже проще сделать параллельный вызов программ-обработчиков. Да, это тоже может сделать внешние программы, скажем, нестандартный xargs. Но в таком случае find просто вывалит список найденных файлов одним потоком для такого скрипта.

vodz ★★★★★ ()
Ответ на: комментарий от futurama

Hy-Hy

Вот именно, что ну-ну. Берут хитрый или не очень скрипт, упрощают под свои задачи и рожают кадавров, а потом и тиражируют. Вот и получается, что вместо цикла со '*' юзают внешнюю программу через пайп, потом опять же через цикл считывают (да еще не со стандартным ключём у read), тормозно, запутанно и почему-то считают, что так и надо.

vodz ★★★★★ ()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от PawsOnFire

Я не зря find начал использовать.

Я не говорю, что не надо никогда юзать find. Я показал, что именно для вашего примера всё решается просто и элегантно. Вы же по вложенности идёте вторым find-ом. Более того, если у вас будет хитрая логика, когда для одного каталога надо вложенность, а для другого - нет, то find вообще будет только мешать, а скриптом получится запрограммировать эту логику просто и непосредственно.

vodz ★★★★★ ()
Ответ на: комментарий от DELIRIUM

Ну не знаю, я лично сталкивался с такой ситуацией, где Шелл ругался на звёздочку, а через find ok. Но это было на соляре.

Оно и видно, что не знаете и не понимаете. Ошибка превышения количества аргументов возникает при вызове внешней программы, для циклов ограничение одно - количество памяти для самого bash-а.

vodz ★★★★★ ()
Ответ на: комментарий от DELIRIUM

Это ограничение линукса (ядра), на размер argv при вызове exec(). У самого по себе шелла внутри такого ограничения нет

mkdir /tmp/argument_list_test
cd /tmp/argument_list_test
seq 1 1000000 | xargs touch
rm * # ошибка rm: Argument list too long
for i in *; do rm "$i"; done # работает
find -delete # работает
find -exec rm {} + # работает
find -print0 | xargs -0 rm # работает
legolegs ★★★★★ ()
Последнее исправление: legolegs (всего исправлений: 1)
Ответ на: комментарий от PawsOnFire

Libreoffice writer испортил много документов

Исходник и испорченный документ - https://gofile.io/d/CJCN2q

Было такое, пришлось написать питоно-скрипт. Скинул исходники на pastebin А вот фиг тебе. Жди помощи от регистранов, раз закрыл доступ анонам.

anonymous ()