How do handle filename space and Character with FIND command - bash

Good day all,
I am working on a bash script to merge multiple mp3 files to one. The code is working fine but cannot handle file name with space or Characters. Below is the code. Can you please tell me what I'm doing wrong. Thanks
for file in $(find . -type f -name "*.mp3" -print0 | xargs -0 ); do
ffmpeg -i "concat:intro.mp3|"$file"|outro.mp3" -acodec copy "${file%.mp3}-".mp3;
done

find has an -exec flag that allows you to call scripts with the search results.
e.g. creating a simple helper-script addxtros.sh:
#!/bin/sh
infile=$1
outfile=${infile%.mp3}-.mp3
ffmpeg -i "concat:intro.mp3|${infile}|outro.mp3" -acodec copy "${outfile}"
you can use it like:
find . -type f -name "*.mp3" -exec ./addxtros.sh {} ";"

read builtin with -d followed by empty argument to use NUL caracter as input record delimiter
while IFS= read -r -d '' file; do
ffmpeg -i "concat:intro.mp3|$file|outro.mp3" -acodec copy "${file%.mp3}-".mp3;
done < <(find . -type f -name "*.mp3" -print0)

You can use find together with bash -c command (that allows passing 2 arguments to ffmpeg):
find . -type f -name "*.mp3" -exec bash -c 'ffmpeg -i "concat:intro.mp3|$1|outro.mp3" -acodec copy "${1%.mp3}-.mp3"' _ {} \;

Related

Bash loop over directory tree with ffmpeg, wrong spaces? [duplicate]

This question already has answers here:
Iterate over a list of files with spaces
(12 answers)
How to loop through file names returned by find?
(17 answers)
Closed 5 years ago.
I am trying to interate over multiple video files that are grouped in directories, and ffmpeg returns errors about paths, it seems like paths are broken, end at first space. Can you point me too what is the problem here? Files and directories have spaces.
$ for f in $(find -type f -name *.mkv); do ffmpeg -n -i "$f" -c:v copy "~/Pobrane/$f" ; done
Loop splits paths by space and takes words as entries. How to fix this?
$ for f in $(find -type f -name *.mkv); do echo "$f"; done
./homeworks/2017-04-03
00-54-57
homework3b.mkv
./homeworks/2017-04-03
00-21-36
homework1.mkv
./homeworks/2017-04-03
Replacing the Loop
Use
find -type f -name '*.mkv' -exec ffmpeg -n -i {} -c:v copy ~/Pobrane/{} \;
-exec executes the following ffmpeg command for each of the found paths, replacing {} with the current path. The ; informs find that the ffmpeg command ends there.
Quote '*.mkv' in order to pass the literal string to find, which then searches for files ending with *.mkv. If you do not quote the string and have some mkv files laying around in your working directory, the shell will expand the unquoted *.mkv resulting in find -type f -name firstFile.mkv secondFile.mkv ... before starting find.
Do not quote ~. The unquoted ~ expands to your home directory (probably /home/yourname) but the quoted '~' is a directory/file with the literal name ~ .
Creating Parent Directories
How would I add mkdir -p before the ffmpeg call?
You could wrap the mkdir and ffmpeg in one function and execute the function:
myFunction() {
mkdir -p "$(dirname "$1")"
ffmpeg -n -i "$1" -c:v copy ~/Pobrane/"$1"
}
export -f myFunction
find -type f -name '*.mkv' -exec bash -c 'myFunction "$0"' {} \;
or use a loop:
find . -type f -iname "*.txt" -print0 | while IFS= read -r -d $'\0' file; do
mkdir -p "$(dirname "$file")"
ffmpeg -n -i "$file" -c:v copy ~/Pobrane/"$file"
done

How to use xargs to replace 2 arguments

I would like to write a script to convert svg files in a directory to png using svgexport cli
svgexport input.svg input.jpg
How can I use find and xargs -I {} to find and print out the svg files using the following:
find . -iname -0 "*.svg" | xargs -I {} svgexport {} ???
How can I fill-in the second argument by using the first argument and replacing .svg with .jpg?
You can use bash -c in xargs and use BASH's string replacement:
find . -name "*.svg" -print0 |
xargs -0 -I {} bash -c 'svgexport "$1" "${1%.svg}.jpg"' - {}
I think it's best to do this with a while loop:
find . -iname "*.svg" -print0 |
while IFS= read -r -d '' file; do
svgexport "$file" "${file%.svg}.jpg"
done
Do them all simply, and faster, in parallel with GNU Parallel
parallel --dry-run svgexport {} {.}.jpg ::: *.svg
Remove the --dry-run if you like what it shows you and run it again to actually process the files.
You can avoid xargs when you use find. It already provides the same feature a simpler way (-exec with + terminator):
find . -iname "*.svg" -exec \
bash -c 'for i do svgexport "$i" "${i::-3}jpg";done' bash {} +
Should you don't want to recurse in subdirectories:
find . -maxdepth 1 -iname "*.svg" -exec \
bash -c 'for i do svgexport "$i" "${i::-3}jpg";done' bash {} +
but in that case, find is not necessary either:
for i in *.[sS][vV][gG]; do svgexport "$i" "${i::-3}jpg"; done
If the suffix is always in lowercase, this can be slightly simplified:
for i in *.svg; do svgexport "$i" "${i%svg}jpg"; done
If your bash version doesn't support ${i::-3}, you can use the portable {i%???} instead.
Should you want to avoid the bash and find GNUisms, here is a POSIX way to achieve the recursive processing:
find . -name "*.[Ss][Vv][Gg]" -exec \
sh -c 'for i do svgexport "$i" "${i%???}jpg";done' sh {} +
and another for the non recursive one:
for i in *.svg; do svgexport "$i" "${i%???}jpg"; done
Easy:
find . -iname "*.svg" -print0 | xargs -0 -I \
bash -c 'export file="{}"; svgexport "$file" "${file%.*}.jpg"'
Add -print0 to find and -0 to xargs to deal with special filenames.
${file%.*} removes all chars after the last dot, so it will remove ".svg" and you can add the new file extension.

Bash script to apply operation to each file found by find

I'm trying to execute an operation to each file found by find - with a specific file extension (wma). For example, in python, I would simply write the following script:
for file in os.listdir('.'):
if file.endswith('wma'):
name = file[:-4]
command = "ffmpeg -i '{0}.wma' '{0}.mp3'".format(name)
os.system(command)
I know I need to execute something similar to
find -type f -name "*.wma" \
exec ffmpeg -i {}.wma {}.mp3;
But obviously this isn't working or else I wouldn't be asking this question =]
Most of the time it's better to use read when parsing input than doing word splitting with for and depending on IFS as there's risk with unexpected pathname expansion.
while IFS= read -u 4 -r LINE; do
ffmpeg -i "$LINE" "${LINE%.*}.mp3"
done 4< <(exec find -type f -name '*.wma')
Or use readarray (Bash 4.0+)
readarray -t FILES < <(exec find -type f -name '*.wma')
for FILE in "${FILES[#]}"; do
ffmpeg -i "$FILE" "${FILE%.*}.mp3"
done
Sticking to the basics always gets the job done (does not handle spaces in filenames):
for f in $(find "." -type f -name "*.wma"); do ffmpeg -i "$f" "${f//wma/mp3}"; done
Starting from konsolebox's suggestions below, I've come up with this complete version:
find "." -type f -name "*.wma" | while read -d $'\n' f; do ffmpeg -i "$f" "${f//wma/mp3}"; done

Get the filename from bash script recursively using find

I am trying retrieve the filename from the find command recursively. This command prints all the filenames with full path
> for f in $(find -name '*.png'); do echo "$f";done
> ./x.png
> ./bg.png
> ./s/bg.png
But when i try to just get the name of the file using these commands, it prints
for f in $(find -name '*.png'); do echo "${f##*/}";done
bg.png
and
for f in $(find -name '*.png'); do echo $(basename $f);done
bg.png
It omits other 2 files. I am new to shell scripting. I couln't figure out whats wrong with this one.
EDIT:
THis is what i have actually wanted.
I want to loop through a directory recursively and find all png images
send it to pngnq for RGBA compression
It outputs the new file with orgfilename-nq8.png
send it to pngcrush and rename and generate a new file (org file will be overwritten )
remove new file
i have a code which works on a single directory
for f in *.png; do pngnq -f -n 256 "$f" && pngcrush "${f%.*}"-nq8.png "$f";rm "${f%.*}"-nq8.png; done
I want to do this recursively
Simply do :
find -name '*.png' -printf '%f\n'
If you want to run something for each files :
find -name '*.png' -printf '%f\n' |
while read file; do
# do something with "$file"
done
Or with xargs :
find -name '*.png' -printf '%f\n' | xargs -n1 command
Be sure you cannot use find directly like this :
find -name '*.png' -exec command {} +
or
find -name '*.png' -exec bash -c 'do_something with ${1##*/}' -- {} \;
Search -printf on http://unixhelp.ed.ac.uk/CGI/man-cgi?find or in
man find | less +/^' *-printf'

Bash script to search and rename recursively

I have a bash script that converts *.mkv files to *.avi files. Here's what it looks like:
#!/bin/bash
for f in $(ls *mkv | sed ‘s/\(.*\)\..*/\1/’)
do
ffmpeg -i $f.mkv -sameq $f.avi
done
What I need this script to do however, is it needs to search recurssively in all folders for *.mkv files and then run the ffmpeg command and save the output to the same directory.
PLEASE can someone help me? :-)
find /some/path -name '*.mkv' | while read f
do
ffmpeg -i "$f" -sameq "${f:0:-4}.avi"
done
Try like this:
find <file_path> -name '*.mkv' -exec sh -c 'mv "$0" "${0%%.mkv}.avi"' {} \;
#!/bin/bash
find . -name "*.mkv" -exec ffmpeg -i {} -sameq `basename {} .mkv`.avi \;
Thanks to #Raul this is what worked for me and is the solution to what I wanted to do which is run recursively through directories and run the ffmpeg command on mkv files:
#!/bin/bash
find <file_path> -name '*.mkv' -exec sh -c 'ffmpeg -i "$0" -sameq "${0%%.mkv}.avi"' {} \;
exit;
Instead of ls *.mkv use find . -name "*.mkv".
This assumes no funny filenames (no spaces, newlines). Another possibility is using find in conjunction with xargs. The xargs manual makes for an instructive reading which will save your scripting life one day :-)

Resources