LINUX.ORG.RU

Исчезновение текущего каталога в пути к файлу в Bash-скрипте

 , , ,


1

1

Пытаюсь перевести библиотеку звуковых файлов в WAV-формат. Для этого написал простой скрипт:

#!/bin/bash
     
find \( -name \*.mp3 \
     -or -name \*.MP3 \
     -or -name \*.Mp3 \
     -or -name \*.ogg \
     \) -print0 \
     | while IFS= read -r -d '' file; 
do
    fileWithoutExt="${file%.*}"
    command="ffmpeg -loglevel quiet -i \"${file}\" -y -ar 44100 \"${fileWithoutExt}.wav\""
    echo "Команда: ${command}"
done

Этот скрипт выдает следующую последовательность команд, и эти команды правильные:
Команда: ffmpeg -loglevel quiet -i "./Last love jam.mp3" -y -ar 44100 "./Last love jam.wav"
Команда: ffmpeg -loglevel quiet -i "./WindJam/Minus 4.mp3" -y -ar 44100 "./WindJam/Minus 4.wav"
Команда: ffmpeg -loglevel quiet -i "./WindJam/Minus 3.mp3" -y -ar 44100 "./WindJam/Minus 3.wav"
Команда: ffmpeg -loglevel quiet -i "./WindJam/Minus 2.mp3" -y -ar 44100 "./WindJam/Minus 2.wav"
Команда: ffmpeg -loglevel quiet -i "./WindJam/Minus 1.mp3" -y -ar 44100 "./WindJam/Minus 1.wav"
Команда: ffmpeg -loglevel quiet -i "./Thrash-electronics JAM.mp3" -y -ar 44100 "./Thrash-electronics JAM.wav"
Команда: ffmpeg -loglevel quiet -i "./Jingle Bells - Backing Track.mp3" -y -ar 44100 "./Jingle Bells - Backing Track.wav"
Команда: ffmpeg -loglevel quiet -i "./Road  Jam minus2.mp3" -y -ar 44100 "./Road  Jam minus2.wav"
Команда: ffmpeg -loglevel quiet -i "./Chamba Jam/1.ogg" -y -ar 44100 "./Chamba Jam/1.wav"
Команда: ffmpeg -loglevel quiet -i "./Chamba Jam/5.ogg" -y -ar 44100 "./Chamba Jam/5.wav"
...

Осталось дело за малым: выполнить эти команды. Добавляю в конец цикла строку:
eval "${command}"

И наблюдаю такую дичь:
Команда: ffmpeg -loglevel quiet -i "./Last love jam.mp3" -y -ar 44100 "./Last love jam.wav"
Команда: ffmpeg -loglevel quiet -i "WindJam/Minus 4.mp3" -y -ar 44100 "WindJam/Minus 4.wav"
Команда: ffmpeg -loglevel quiet -i "/WindJam/Minus 3.mp3" -y -ar 44100 "/WindJam/Minus 3.wav"
Команда: ffmpeg -loglevel quiet -i "./WindJam/Minus 2.mp3" -y -ar 44100 "./WindJam/Minus 2.wav"
Команда: ffmpeg -loglevel quiet -i "/WindJam/Minus 1.mp3" -y -ar 44100 "/WindJam/Minus 1.wav"
Команда: ffmpeg -loglevel quiet -i "./Thrash-electronics JAM.mp3" -y -ar 44100 "./Thrash-electronics JAM.wav"
Команда: ffmpeg -loglevel quiet -i "/Jingle Bells - Backing Track.mp3" -y -ar 44100 "/Jingle Bells - Backing Track.wav"
Команда: ffmpeg -loglevel quiet -i "./Road  Jam minus2.mp3" -y -ar 44100 "./Road  Jam minus2.wav"
Команда: ffmpeg -loglevel quiet -i "/Chamba Jam/1.ogg" -y -ar 44100 "/Chamba Jam/1.wav"
Команда: ffmpeg -loglevel quiet -i "./Chamba Jam/5.ogg" -y -ar 44100 "./Chamba Jam/5.wav"
...

Тут видно, что по неизвестным причинам в командных строках перед именем файла, вместо ожидаемых символов "./", могут быть следующие варианты:

- "./"
- «/»
- «»

Соответственно, обрабатываются не только лишь все файлы.

Я пробовал вместо eval и такие варианты для выполнения сконструированной команды:
"${command}"
и
${command}

Но тогда вообще другая дичь происходит: в первом варианте лезут ошибки (запускал с set -x):
Команда: ffmpeg -loglevel quiet -i "./Last love jam.mp3" -y -ar 44100 "./Last love jam.wav"
+ 'ffmpeg -loglevel quiet -i "./Last love jam.mp3" -y -ar 44100 "./Last love jam.wav"'
./soundConvert05.sh: строка 22: ffmpeg -loglevel quiet -i "./Last love jam.mp3" -y -ar 44100 "./Last love jam.wav": Нет такого файла или каталога

А во втором варианте команда вроде как выполняется, никакой ошибки не показывается, но WAV-файла не появляется:
Команда: ffmpeg -loglevel quiet -i "./Last love jam.mp3" -y -ar 44100 "./Last love jam.wav"
+ ffmpeg -loglevel quiet -i '"./Last' love 'jam.mp3"' -y -ar 44100 '"./Last' love 'jam.wav"'

Хотя опять же, если ручками выделить команду и выполнить ее в консоли, она выполнится правильно и WAV-файл будет сгенерирован.

Вопрос: как в этих ваших линуксах просто выполнить сконструированную команду, которая лежит в переменной?

★★★★★

Походу, нашел. Относительно просто можно выполнить в другом процессе:

(bash -c "${command}")

Он будет запущен так же в текущей директории, так что все команды выполнятся правильно.

Однако и тут есть подводные камни, уже с ffmpeg:

https://unix.stackexchange.com/questions/36310/strange-errors-when-using-ffmp...

http://mywiki.wooledge.org/BashFAQ/089

Я не совсем понял, но внутри цикла ffmpeg «выгребает» входной поток. Как это входной поток оказывается внутри цикла тоже неясно. Что содержит входной поток внутри цикла - тоже загадка.

В любом случае, для ffmpeg надо использовать опцию -nostdin.

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

У команды (bash -c «${command}») вылезла одна особенность: непонятно как через нее запускать многострочные команды, как их писать. Использование символов «\» в местах переноса строки не помогает.

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

Использование символов «\» в местах переноса строки не помогает.

Почему \? Попробуй \n. Ну и присоединяюсь к вопросу выше.

По основному вопросу: может тебе лучше просто в find задать путь, причём абсолютный на всякий случай?

Вместо этого:

find \( -name \*.mp3 \
     -or -name \*.MP3 \
     -or -name \*.Mp3 \
     -or -name \*.ogg \
     \) -print0 \
     | while IFS= read -r -d '' file; 

Это:

find "$(pwd)" \( -iname \*.mp3 -or -name \*.ogg \) -print0 \
| while IFS= read -r -d '' file; do

И оно заработает в изначальном варианте, без путаницы с путями.

P.S. fileWithoutExt="${file%.*}" — спасибо за наглядный пример бесполезного использования переменных. Это примерно как X_PLUS_ONE = x + 1, чтобы использовать потом один раз, только менее избито.

P.P.S. В примере выше есть небольшое отличие в функционале от твоего, помимо перечисленных оно ещё и *.mP3 тоже найдёт, если таковые есть. Если это не желаемое поведение, перепиши эту -or’ную часть обратно.

CrX ★★★★★
()
Последнее исправление: CrX (всего исправлений: 2)

eval

Никогда, блин, не используйте eval для ваших шеллскриптов. Наедитесь говна с кавычками.

/usr/local/bin/any2wav.sh:

#/bin/bash
while [ $# -gt 0 ]; do
    ffmpeg -nostdin -loglevel quiet -i "$1" -y -ar 44100 "${1%.*}.wav"
    shift
done

all2wav.sh:

find \( -iname \*.mp3 \
     -or -iname \*.ogg \
     \) -exec /usr/local/bin/any2wav.sh {} +

Всё.

В bash исполняемый код наиболее естественным образом организуется в исполняемые файлы (и в функции), а не в строки. И для сущности список файлов лучшим типом данных является argv. Как бонус, any2wav всегда можно переделать на многопоточный режим.

legolegs ★★★★★
()
Последнее исправление: legolegs (всего исправлений: 1)

Вполне допустимо сначала сгенерировать список команд как текстовый файл, а затем выполнить его как sh-скрипт. Или даже скормить какому-нибудь parallel или task-spooler, чтобы выполнять команды параллельно.

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

Можно, конечно, но для кодогенерации желательно что-то понимать в баше, а не считать его странным бейсиком.

Но в случае с parallel у ТС всё даже проще, буквально одна строчка:

find \( -iname \*.mp3 -or -iname \*.ogg \) \ 
    -exec parallel --eta -I -- \
    ffmpeg -nostdin -loglevel quiet -i -- -y -ar 44100  {.}.wav ::: {} +
legolegs ★★★★★
()
Последнее исправление: legolegs (всего исправлений: 1)
Ответ на: комментарий от slowpony

что за многострочные команды такие, где перевод строки строго необходим?

ffmpeg, там столько опций приходится писать что по ширине команда в несколько экранов получается

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

По сложившейся в юниксах традиции в переменную кладут не всю команду, а часть опций.

Например так:

FF_IN_OPTS=-nostdin -loglevel quiet
FF_OUT_OPTS=-y -ar 44100
ffmpeg $FF_IN_OPTS -i "$file" $FF_OUT_OPTS "$outfile"

Но это становится неудобным, если в опциях есть пробелы (напр. имена файлов). Поэтому в примере выше я имена задаю отдельно.

Можно всё, включая имена засунуть в массив

FF_OPTS=(
-loglevel quiet
-i "${file}"
-y
-ar 44100
"$outfile"
)
ffmpeg "${FF_OPTS[@]}"

Это будет работать. Хотя я всё-таки рекомендую сделать простой скрипт-обёртку над ffmpeg, как я привёл выше, это проще и понятнее.

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

find «$(pwd)» \( -iname \*.mp3 -or -name \*.ogg \) -print0 \
| while IFS= read -r -d " file; do
И оно заработает в изначальном варианте, без путаницы с путями.

Какой ужосо. Как же любители консоли любят нечитаемые строки километровой длинны.

P.S. fileWithoutExt=«${file%.*}» — спасибо за наглядный пример бесполезного использования переменных. Это примерно как X_PLUS_ONE = x + 1, чтобы использовать потом один раз, только менее избито.

Бесполезного? Ну-ну. Сейчас меня bash-извращенец будет в нейминг учить.

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

Какой ужосо. Как же любители консоли любят нечитаемые строки километровой длинны.

ЛОЛ. Так это ж ты написал километровой длины, я лишь сократил слегка.

Или ты про переносы перед -or? Ну так поставь их сам, лентяй. Я как проверял на всякий случай в живом шелле, так и скопипастил. Нашёл, к чему придираться. По сути-то решение годится, нет? Лучше бы об этом чего сказал, чем к оформлению прикапываться.

А вот do на той же строке, что и while или for — это мне так больше нравится, да. То же самое с then для if. Для меня так читаемее — виднее, где начинается цикл или условие. Но да, это дело вкуса. В принципе то же самое, что и с фигурными скобками в сишке.

Бесполезного? Ну-ну. Сейчас меня bash-извращенец будет в нейминг учить.

Какую пользу оно несёт, если используется ровно один раз и говорит ровно то же самое? Чисто для тех, кто не знает, что значит ${file%.*}? Я не против и в данном случае не учу тебя, просто спасибо сказал. Вот так вот, теперь за вежливость извращенцем обзывают. А так оно выглядит забано. Я себе сохранил, буду использовать как пример наряду с X_PLUS_ONE.

CrX ★★★★★
()
Последнее исправление: CrX (всего исправлений: 5)
#!/bin/bash
CORE="4" # Создавать потоков

if [ "$1" = "-h" ]; then
echo 'Использование: paket_convert.bash <каталог для поиска файлов> <каталог для помещения результатов>'
echo 'Задействовано потоков: '"$CORE"
exit 0
fi
if [ "$1" = "--help" ]; then
echo 'Использование: paket_convert.bash <каталог для поиска файлов> <каталог для помещения результатов>'
echo 'Задействовано потоков: '"$CORE"
exit 0
fi

rm -R /tmp/ffmpeg/
rm /tmp/convert.bash
mkdir /tmp/ffmpeg/
id='1' # Начальный индекс файла
cd "$1"
mkdir "$2"
ALL=$( find -P ./ -type f | wc -l )

	# Создание второстепенного скрипта /tmp/convert.bash
	# $1 - id файла. $2 - путь к папке, куда надо положить результат. Файлы блокировок расположены в /tmp/ffmpeg/, имя = id, содержится строка с отн. адресом файла на перекодирование.
echo 'ALL=$( find -P ./ -type f | wc -l )' >> /tmp/convert.bash
echo 'FILE=$( cat /tmp/ffmpeg/$1 )' >> /tmp/convert.bash
echo 'DIR=${FILE%/*}' >> /tmp/convert.bash
echo 'LONG_DIR=${#DIR}+1' >> /tmp/convert.bash
echo 'NAME=${FILE:LONG_DIR}' >> /tmp/convert.bash
echo 'FILENAME=${NAME%.*}' >> /tmp/convert.bash

# для музыки
#echo 'ffmpeg -i "$FILE" -vn -acodec libvorbis -ac 1 -aq 4 "$2"/"$FILENAME".ogg > /dev/null 2>&1' >> /tmp/convert.bash
#echo 'ffmpeg -i "$FILE" -vn -acodec libmp3lame -aq 6 "$2"/"$FILENAME".mp3 > /dev/null 2>&1' >> /tmp/convert.bash

# для j1mini, 800x480
#echo 'ffmpeg -i "$FILE" -acodec copy -vcodec h264_omx -b:v 1000K "$2"/"$FILENAME".mp4 > /dev/null 2>&1' >> /tmp/convert.bash

echo 'ffmpeg -i "$FILE" -s 648x360 -acodec copy -vcodec libx264 -profile high -level 42 -qmax 22 "$2"/"$FILENAME".mp4 > /dev/null 2>&1' >> /tmp/convert.bash

# для парсера 4PDA
#echo '4pda_pars.bash "$FILE" "$FILENAME"' >> /tmp/convert.bash
#echo '4pda_pars_news.bash "$FILE" >> "$2"/"$NAME"' >> /tmp/convert.bash


echo 'rm /tmp/ffmpeg/"$1"' >> /tmp/convert.bash
echo 'echo $1 из $ALL завершено' >> /tmp/convert.bash
chmod +x /tmp/convert.bash
	# Конец создания второстепенного скрипта

		find -P ./ -type f | while read FILE
		do
while [ $( ls -1A /tmp/ffmpeg | wc -l ) -ge "$CORE" ]; do
	sleep 20
#	sleep 1
done

echo "$FILE" >> /tmp/ffmpeg/"$id"
/tmp/convert.bash "$id" "$2" &
echo $id'/'$ALL" кодируется ""$FILE"
let id++
sleep 2
#sleep 0.2
		done

while [ $( ls -1A /tmp/ffmpeg | wc -l ) -gt "0" ]; do
	sleep 3
done
kirill_rrr ★★★★★
()
Ответ на: комментарий от kirill_rrr

if [ «$1» = "-h" ]; then
echo 'Использование: paket_convert.bash <каталог для поиска файлов> <каталог для помещения результатов>'
echo 'Задействовано потоков: '«$CORE»
exit 0
fi
if [ «$1» = "--help" ]; then
echo 'Использование: paket_convert.bash <каталог для поиска файлов> <каталог для помещения результатов>'
echo 'Задействовано потоков: '«$CORE»
exit 0
fi

По-моему, это за гранью. Но ни у кого, кроме меня, претензий как видим, нету.

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

Там дальше интересней. Внешний скрипт вместо функции и многопоточность через sleep и файлы блокировок. И это работает, по крайней мере у меня в руках.

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

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

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

Я подумал и решил что нефиг держать в памяти функцию и делать операцию копирования одной строки в другую, интерпретатору проще пролистать несработавшие ветки цикла.

kirill_rrr ★★★★★
()