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 ★★★★★
()
Ответ на: комментарий от xaizek

Ну вот я так и думал, что что-то очевидное. Спасибо.

PawsOnFire
() автор топика

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

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)
Ответ на: комментарий от vodz

второй find тоже будешь bash’ем эмулировать? А если первый find надо усложнить? А ты сразу всех с говном мешать. Некрасиво.

futurama ★★★★★
()
Ответ на: комментарий от 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)
Ответ на: комментарий от vodz

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

DELIRIUM ☆☆☆☆☆
()
Ответ на: комментарий от 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)
19 июля 2020 г.
Ответ на: комментарий от PawsOnFire

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

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

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

anonymous
()
6 февраля 2021 г.
Ответ на: комментарий от anonymous

If your looking for Online Illinois license plate sticker renewals then you have need to come to the right place.We offer the fastest Illinois license plate sticker renewals in the state. share photos online

https://usaupload.com/

kalysam
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.