Bash check if file matching regex exists and assign filename to variable - regex

I'm looking for a fast, short and portable way to check if a file matching the regex (env(ironment)?|requirements).ya?ml exists in the current working directory and if so assign its basename to a variable $FILE for further processing.
Basically, I'd like to combine getting the handle in
for FILE in environment.yml env.yml requirements.yml environment.yaml env.yaml requirements.yaml; do
if [ -e $FILE ]; then
...
fi
done
with using a regex as in
if test -n "$(find -E . -maxdepth 1 -regex '.*(env(ironment)?|requirements).ya?ml' -print -quit)"
then
...
fi

Stick it in a variable:
file="$(find -E . -maxdepth 1 -regex '.*(env(ironment)?|requirements).ya?ml' -print -quit)"
if [ -n "$file" ]
then
echo "I found $file"
else
echo "No such file."
fi
Alternatively, you can keep your loop and shorten it using brace expansion:
for file in {env{,ironment},requirements}.{yml,yaml}
do
if [ -e "$file" ]
then
echo "Found $file"
else
echo "There is no $file"
fi
done
or match files directly using bash's extglob:
shopt -s nullglob
for file in #(env?(ironment)|requirements).y?(a)ml
do
echo "Found $file"
done

Related

Getting specific text from filenames in Bash script

I'm trying to write bash script, in which I could get the specific text from all filenames in directory, e.g
I have files in my folder:
.\file1.txt
.\file2.hex
.\DSConf-11.22.33[4444].pkg
.\file3.cpp
What I need to do is taking 4444 number from above filenames. I created a script but it doesn't work and I can't find mistake:
#!/usr/bin/bash
FILENAME=$(find . -maxdepth 1 -name "DSConf-*.*.*\[*\].pkg")
PATTERN='^DSConf-(\d+).(\d+).(\d+)\[([^)]+)\]'
[[ $FILENAME =~ $PATTERN ]]
echo "${BASH_REMATCH[4]}"
for f in ./; do echo "$f" | grep -Eo "DSConf-\d+.\d+.\d+\[([^)]+)\]"; done
will generate your output.
You may look for files with names starting with DSConf- and end with .pkg using find -name "DSConf-*" and then get the digits between square brackets using sed:
find . -maxdepth 1 -name "DSConf-*.pkg" | sed -n 's/.*\[\([0-9]*\)].*/\1/p'
The sed -n 's/.*\[\([0-9]*\)].*/\1/p' command suppresses sed line output using -n, then s/.*\[\([0-9]*\)].*/\1/p substitutes the whole string with just the 0 or more digits between [ and ] and p prints that number.

Weird regex behavior in bash if condition

I have written a small script that loops through directories (starting from a given argument directory) and prompts directories that have an xml file inside. Here is my code :
#! /bin/bash
process()
{
LIST_ENTRIES=$(find $1 -mindepth 1 -maxdepth 1)
regex="\.xml"
if [[ $LIST_ENTRIES =~ $regex ]]; then
echo "$1"
fi
# Process found entries
while read -r line
do
if [[ -d $line ]]; then
process $line
fi
done <<< "$LIST_ENTRIES"
}
process $1
This code works fine. However, if I change the regex to \.xml$ to indicate that it should match at the end of the line, the result is different, and I do not get all the right directories.
Is there something wrong with this ?
Your variable LIST_ENTRIES may not have .xml as the last entry.
To validate, try echo $LIST_ENTRIES.
To overcome this, use for around your if:
process()
{
LIST_ENTRIES=$(find $1 -mindepth 1 -maxdepth 1)
regex="\.xml$"
for each in $LIST_ENTRIES; do
if [[ $each =~ $regex ]]; then
echo "$1"
fi
done
# Process found entries
while read -r line
do
if [[ -d $line ]]; then
process $line
fi
done <<< "$LIST_ENTRIES"
}
process $1

Shell script to rename multiple files from their parent folders

I'm looking for a script for below structure:
Before :
/Description/TestCVin/OpenCVin/NameCv/.....
/Description/blacVin/baka/NameCv_hubala/......
/Description/CVintere/oldCvimg/NameCv_add/.....
after:
/Description/TestaplCVin/OpenaplCVin/NameaplCv/.....
/Description/blaapcVlin/baka/NameaplCv_hubala/......
/Description/aplCVintere/oldaplCvimg/NameaplCv_add/.....
I want to rename " Cv or CV or cV " >> "aplCv or aplCV or aplcV" in all folder by regular expression...
My script does look like:
#!/bin/sh
printf "Input your Directory path: -> "
read DIR
cd "$DIR"
FILECASE=$(find . -iname "*cv*")
LAST_DIR_NAME=""
for fdir in $FILECASE
do
if [[ -d $fdir ]];
then
LAST_DIR_NAME=$fdir
fi
FILE=$(echo $fdir | sed -e "s/\([Cc][Vv]\)/arpl\1/g")
echo "la file $FILE"
if ([[ -f $fdir ]] && [[ "$fdir" =~ "$LAST_DIR_NAME" ]]);
then
FILECASE=$(find . -iname "*cv*")
tmp=$(echo $LAST_DIR_NAME | sed -e "s/\([Cc][Vv]\)/arpl\1/g")
fdir=$(echo $fdir | sed -e 's|'$LAST_DIR_NAME'|'$tmp'|g')
fi
mv -- "$fdir" "$FILE"
done
But it throws an error ..:(
How could I write it to rename the files according to their folder names?
You can do like this
#!/bin/sh
printf "Input your Directory path: -> "
read DIR
cd "$DIR"
MYARRAY=$(find . -iname "*cv*" )
touch "tmpfile"
for fdir in $MYARRAY
do
echo "$fdir" >> "tmpfile"
done
MYARRAY=$(tac "tmpfile")
for fdir in $MYARRAY
do
cd "$fdir"
prev=$(cd -)
base=$(basename $fdir)
cd ..
nDIR=$(echo "$base" | sed -e "s/\([Cc][Vv]\)/arpl\1/g")
mv "$base" "$nDIR"
cd $prev
done
rm -f "tmpfile"
Also one issue i think tac command not included in Mac OS X.Instead tac use tail -r like MYARRAY=$(tail -r "tmpfile")
Always make a backup before playing with this kind of scripts.
You can try the following:
find . -iname '*cv*' -exec echo 'mv {} $(echo $(dirname {})/$(basename {}|sed s/cv/apl/gi))' \;|tac|xargs -i bash -c 'eval {}'
This uses -exec to print commands for renaming.
The second arguments are generated by using shell substitutions to replace cv with apl in the last part of the path.
tac is used to reverse the order of the commands, so that we do not rename a directory before working with its contents.
Finally, we eval the commands with bash.
Also, do not use -exec in a permanent script. Please read the security warnings about exec in the find man-page.

Determining correct extension in bash with Regular Expressions

Was wondering if someone could help me out with regular expressions and bash.
I'm trying to execute a set of commands on files that only have a certain extension, in this case: mpg, mpeg, avi, and mkv.
I've actually found a solution here, however, it doesn't seem to work. If someone can tell me why, I'd appreciate it.
#!/bin/bash
# Configuration
TARGETDIR="$1"
TARGETEXT="(mpg|mpeg|avi|mkv)"
for d in `find $1 -type d`
do
echo "Searching directory: $d"
for f in "$d"/*
do
if [ -d "${f}" ];
then
# File is a directory, do not perform
echo "$f is a directory, not performing ..."
elif [ -f "${f}" ];
then
filename=$(basename "$f")
extension="${filename##*.}"
if [ "$extension" == "$TARGETEXT" ];
then
echo "Match"
else
echo "Mismatch - $f - $extension"
fi
fi
done
done
Again, any assistance is appreciated.
This can probably be done using only the find command.
find $TARGETDIR -regex ".*\\.$TARGETEXT" -type f -exec your_command {} \;
Instead of direct string comparison
if [ "$extension" == "$TARGETEXT" ];
use Bash regex matching syntax
if [[ "$extension" =~ $TARGETEXT ]];
Note the double [[ ]] and the non-quoted $TARGETEXT.
You can do this in bash without regular expressions, just file patterns:
shopt -s globstar nullglob
for f in **/*.{mpg,mpeg,avi,mkv}; do
if [[ -f "$f" ]]; then
# do something with the file:
echo "$f"
fi
done

Sed and grep regex syntaxes differ

This is a part of my shell script, which I use to perform a recursive find and replace in the working directory. Backup and other utilities are in other functions, which are irrelevant to my problem.
#!/bin/bash
# backup function goes here
# #param $1 The find pattern.
# #param $2 The replace pattern.
function findAndReplace {
bufferFile=/tmp/tmp.$$
filesToReplace=`find . -type f | grep -vi cvs | grep -v '#'`
sedPattern="s/$1/$2/g"
echo "Using pattern $sedPattern"
for f in $filesToReplace; do
echo "sedding file $f"
sed "$sedPattern" "$f" > "$bufferFile"
exitCode=$?
if [ $exitCode -ne 0 ] ; then
echo "sed $sedPattern exited with $exitCode"
exit 1
fi
chown --reference=$f $bufferFile
mv $bufferFile $f
done
}
backup
findAndReplace "$1" "$2"
Here's a sample usage: recursive-replace.sh "function _report" "function report".
It works, but there is one problem. It uses sed on ALL files in the working directory. I would like to sed only those files, which contain the find pattern.
Then, I modified the line:
filesToReplace=`find . -type f | grep -vi cvs | grep -v '#'`
to:
filesToReplace=`grep -rl "$1" . | grep -vi cvs | grep -v '#'`
And it works too, but not for all find patterns. E.g. for pattern \$this->report\((.*)\) I recieve error: grep: Unmatched ( or \(. This pattern is correct for sed, but not for grep.
Regex syntaxes for grep and sed differ. What can I do?
use grep -E ("extended" regexp option) — it usually solves the problem.
(also sometimes available as egrep)
Also, why not keep using find?
filesToReplace=`find . -name CVS -prune -o -type f -exec grep -l "$1" {} \; | grep -v '#'`
Also note the -i option of sed, which allows in-place changes in files and the removal of the bufferFile/chown/mv logic.
Why not compare source and buffer files before overwriting the source file:
#!/bin/bash
# backup function goes here
# #param $1 The find pattern.
# #param $2 The replace pattern.
function findAndReplace {
bufferFile=/tmp/tmp.$$
filesToReplace=`find . -type f | grep -vi cvs | grep -v '#'`
sedPattern="s/$1/$2/g"
echo "Using pattern $sedPattern"
for f in $filesToReplace; do
echo "sedding file $f"
sed "$sedPattern" "$f" > "$bufferFile"
exitCode=$?
if [ $exitCode -ne 0 ] ; then
echo "sed $sedPattern exited with $exitCode"
exit 1
fi
cmp -s $f $bufferFile
if [ $? -ne 0 ]; then
chown --reference=$f $bufferFile
mv $bufferFile $f
fi
done
}
backup
findAndReplace "$1" "$2"