Bash script to convert music from one directory into another - bash

I've modified this script from the arch forums: https://wiki.archlinux.org/index.php/Convert_Flac_to_Mp3#With_FFmpeg
I'm trying to find specific file types in a directory structure, convert them to another music file type, and place them in a "converted" directory that maintains the same directory structure.
I'm stuck at stripping the string $b of its file name.
$b holds the string ./converted/alt-j/2012\ an\ awesome\ wave/01\ Intro.flac
Is there a way I can remove the file name from the string? I don't think ffmpeg can create/force parent directories of output files.
#!/bin/bash
# file convert script
find -type f -name "*.flac" -print0 | while read -d $'\0' a; do
b=${a/.\//.\/converted/}
< /dev/null ffmpeg -i "$a" "${b[#]/%flac/ogg}"
#echo "${b[#]/%flac/ogg}"

I'm stuck at stripping the string $b of its file name.
Let us start with b:
$ b=./converted/alt-j/2012\ an\ awesome\ wave/01\ Intro.flac
To remove the file name, leaving the path:
$ c=${b%/*}
To verify the result:
$ echo "$c"
./converted/alt-j/2012 an awesome wave
To make sure that directory c exists, do:
$ mkdir -p "$c"
Or, all in one step:
$ mkdir -p "${b%/*}"
How it works
We are using the shell's suffix removal feature. In the form ${parameter%word}, the shell finds the shortest match of word against the end of parameter and removes it. (Note that word is a shell glob, not a regex.) In out case, word is /* which matches a slash followed by any characters. Because this removes the shortest such match, this removes only the filename part from the parameter.
Suffix Removal Detailed Documentation
From man bash:
${parameter%word} ${parameter%%word}
Remove matching suffix pattern. The word is expanded to produce a pattern just as in pathname expansion. If the pattern
matches a trailing portion of the expanded value of parameter, then the result of the expansion is the expanded value of
parameter with the shortest matching pattern (the %'' case) or the longest matching pattern (the%%'' case) deleted.
If parameter is # or *, the pattern removal operation is applied to each positional parameter in turn, and the expansion
is the resultant list. If parameter is an array variable subscripted with # or *, the pattern removal operation is
applied to each member of the array in turn, and the expansion is the resultant list.

Related

Replace first character of file in folder from Uppercase to Lower case

I'm trying to convert 3,000 or so .svg files from CapitalCase to camelCase.
Current:
-Folder
--FileName1
--FileName2
--FileName3
Goal:
-Folder
--fileName1
--fileName2
--fileName3
How can I use terminal to change the casing on the first character with to lowercase?
Currently I've been trying something along these lines: for f in *.svg; do mv -v "$f" "${f:1}"; done
All files in the folder start with a letter or number.
This can be done very succinctly in zsh with zmv:
autoload zmv
zmv -nvQ '(**/)(?)(*.svg)(.)' '$1${(L)2}$3'
This will recurse through any number of directory levels, and can handle name collisions and other edge cases.
Some of the pieces:
-n: no execution. With this option, zmv will only report what changes it would make. It's a dry run that can be used to test out the patterns. Remove it when you're ready to actually change the names.
-v: verbose.
-Q: qualifiers. Used to indicate that the source pattern includes a glob qualifier (in our case (.)).
'(**/)(?)(*.svg)(.)': source pattern. This is simply a regular zsh glob pattern, divided into groups with parentheses. The underlying pattern is **/?*.svg(.). The pieces:
(**/): directories and subdirectories. This will match any number of directory levels (to only affect the current directory, see below).
(?): matches a single character at the start of the file name. We'll convert this to lowercase later.
(*.svg): matches the rest of the file name.
(.): regular files only. This is a zsh glob qualifier; zmv recognizes it as a qualifier instead of a grouping because of the -Q option. The . qualifier limits the matching to regular files so that we don't try to rename directories.
'$1${(L)2}$3': destination pattern. Each of the groupings in the source pattern is referenced in order with $1, $2, etc.
$1: the directory. This could contain multiple levels.
${(L)2}: The first letter in the file name, converted to lowercase. This uses the L parameter expansion flag to change the case.
The l expansion modifier will also work: $2:l.
The conversion can handle non-ASCII characters, e.g. Éxito would
become éxito.
$3: the rest of the file name, including the extension.
Variations
This will only change files in the current directory:
zmv -nv '(?)(*.svg)' '$1:l$2'
The source pattern in the following version will only match files that start with an uppercase letter. Since the zmv utility won't rename files if the source and destination match, this isn't strictly necessary, but it will be slightly more efficient:
zmv -nvQ '(**/)([[:upper:]])(*.svg)(.)' '$1${(L)2}$3'
More information
zmv documentation:
https://zsh.sourceforge.io/Doc/Release/User-Contributions.html#index-zmv
zsh parameter expansion flags:
https://zsh.sourceforge.io/Doc/Release/Expansion.html#Parameter-Expansion-Flags
Page with some zsh notes, including a bunch of zmv examples:
https://grml.org/zsh/zsh-lovers.html
Solving in bash, tested and working fine, be careful though with your files you working on.
Renaming files in current directory where this script is (1st arg then'd be .) or provide a path, it's do lower-casing of the first letter, if it was uppercase, and yet nothing if it was a number, argument must be provided:
# 1 argument - folder name
# 2 argument - file extension (.txt, .svg, etc.)
for filename in $(ls "$1" | grep "$2")
do
firstChar=${filename:0:1}
restChars=${filename:1}
if [[ "$firstChar" =~ [A-Z] ]] && ! [[ "$firstChar" =~ [a-z] ]]; then
toLowerFirstChar=$(echo $firstChar | awk '{print tolower($0)}')
modifiedFilename="$toLowerFirstChar$restChars"
mv "$1/$filename" "$1/$modifiedFilename"
else
echo "Non-alphabetic or already lowercase"
# here may do what you want fith files started with numbers in name
fi
done
Use: bash script.sh Folder .txt
ATTENTION: Now here after running script and renaming, names of some files may coincide and there would be a conflict in this case. Can later fix it and update this script.

what's the meaning of "${0##*[\\/]}" in shell?

I got the question when i looking other's shell script.
I saw that declared
APP_NAME="${0##*[\\/]}"
Since I can't find any answer, what's the meaning of this code?
It's Shell Parameter Expansion to get script name itself, without path
See bash manual:
${parameter##word}
The word is expanded to produce a pattern and matched according to the rules described below (see Pattern Matching).
If the pattern matches the beginning of the expanded value of parameter, then the result of the expansion is the expanded value of parameter with the shortest matching pattern (the ‘#’ case) or the longest matching pattern (the ‘##’ case) deleted.
Pattern Matching:
*
Matches any string, including the null string. When the globstar shell option is enabled, and ‘*’ is used in a filename expansion context
[…]
Matches any one of the enclosed characters.
Explanation
${parameter<...>} expression means that you can expand shell parameters.
I.e. ${1:-"default_arg_value"} will be expanded to "default_arg_value" if script running without arguments.
0 - is a 0th argument, i.e. script name itself
${0##<pattern>} will delete longest matching to <pattern> part of $0
*[\\/] means any string that ends with \ or / symbol.
So, APP_NAME="${0##*[\\/]}" means that $APP_NAME will be initialized by script name itself, without path.
Sample
Let's suppose you have script a/b/c/d/test.sh:
#!/bin/bash
echo "${0##*[\/]}"
echo "${1#*[\/]}"
echo "${2##[\/]}"
$ bash a/b/c/d/test.sh /tmp/e /tmp/ff
> test.sh
> tmp/e
> tmp/ff

Moving files containing spaces with mv command

I have the following input.
Entities can be 'MG SOCRATES' 'VALIND' 'SUSBTECT'
This entities have the following directories.
/home/tca/git/tca/<entity>
Example :
/home/tca/git/tca/MG SOCRATES
/home/tca/git/tca/VALIND
/home/tca/git/tca/SUSBTECT
In each directory I have files with this pattern something_or-something.
For example:
/home/tca/git/tca/SUSBTECT/asdsad2018-01-01-2018-12-31sdadsda
/home/tca/git/tca/SUSBTECT/asdsad2018-01-01_2018-12-31sdsadadsda
I want to move them to a subdicretory for each entity with the following structure.
I have for example this subdirectory.
/home/tca/git/tca/SUSBTECT/2018-01-01_2018-12-31
What I want to do is to move from /home/tca/git/tca/SUSBTECT/ all files that matches the pattern _or- to the subdirectory.
My code does it correctly but fails for entity 'MG SOCRATES' because there is a space that moves command can not interpretate.
My code:
entity_path="$entity_path""/*"$file_start"*"$file_end"*"
echo `mv -t "$path" $entity_path`
Where $entity_path all files matching the pattern and $path is the directory where I want to move my files.
I think is a problem about the spaces.
Any idea?
There is no way to have a regular parameter expansion that undergoes pathname expansion, but not word-splitting, so your attempt to put the pattern in entity_path is going to fail. Either use the pattern directly,
mv -t "$path" "$entity_path"/*"$file_start"*"$file_end"*
or store the result of the pathname expansion in an array.
entities=( "$entity_path"/*"$file_start"*"$file_end"* )
mv -t "$path" "${entities[#]}"

Search a directory for files with fixed name pattern

I want to search a directory (let's call it "testDir") for files which names start with a letter "a", have letter "z" at fourth position and their file extension is .html.
Is there any way to use grep for this? How can I search for a character at fixed index?
You can use native Bash pattern matching: a??z*.html. This pattern means exactly what you're asking for:
Start with the letter "a"
Followed by any two characters
Followed by the letter "z" (4th position)
Followed by 0 or more characters
Ending with ".html"
You can get the matching filenames with any shell tool that prints filenames when passed as arguments.
Some examples:
ls testDir/a??z*.html or echo testDir/a??z*.html. Note that these will print with the testDir/ prefix.
(cd testDir && echo a??z*.html) will print just the filenames without the testDir/ prefix.
Note that the ls command will produce an error when there are no matching files, while the echo command will print the pattern (a??z*.html).
For more details on pattern matching, see the Pattern Matching section in man bash.
If you are looking for an alternative that produces no output when there are no matches, grep will be easier to use, but grep uses different syntax for matching pattern, it uses regular expressions.
The same pattern written in regular expressions is ^a..z.*\.html$.
This breaks down to:
^ means start of line, so ^a means to start with "a"
. is any character, precisely one
.* is 0 or more of any character
\. is a "."
$ means end of line, so html$ means to end with "html"
Here's one way to apply it to your example:
(cd testDir && ls | grep '^a..z.*\.html$')
How about this:
ls -d testDir/a??z* |grep -e '.html$'

Bash script - Retrieve every value that fills *

I have a bash script that's like
for i in /path/to/file/*.in; do
./RunCode < "$i"
done
I want to be able to capture the output in something like *.out, with * being the same as *.in. How can I retrieve the text that * is expanded into so that I can reuse it?
By the wording in your question (could be clearer), I assume you wish remove the leading path.
You can use parameter expansion to accomplish what you want:
out_dir="/path/out"
for i in /path/to/file/*.in; do
name="${i##*/}"
./RunCode < "$i" > "$out_dir/${name%.in}.out"
done
This will remove the leading path and the .in extension, name all output files with .out extension, and place them in the directory /path/out.
${i##*/} - Removes all leading characters through the last occurrence of / to get the name of the file with the .in extension.
${name%.in}.out - Removes the trailing .in extension from name and replaces with .out.
Change file suffix with bash:
for i in /path/to/file/*.in; do
./RunCode < "$i" > "${i%.in}.out"
done
From man bash:
${parameter%word}
${parameter%%word}
Remove matching suffix pattern. The word is expanded to produce a
pattern just as in pathname expansion. If the pattern matches a
trailing portion of the expanded value of parameter, then the result
of the expansion is the expanded value of parameter with the shortest
matching pattern (the ``%'' case) or the longest matching pattern
(the ``%%'' case) deleted. If parameter is # or *, the pattern removal
operation is applied to each positional parameter in turn, and the
expansion is the resultant list. If parameter is an array variable
subscripted with # or *, the pattern removal operation is applied
to each member of the array in turn, and the expansion is the resultant
list.

Resources