So, i create a bash script that search recursively for files that includes the words that you introduce, then creates a folder (if the folder does not exist) with the same name as the variable introduced and finally it copies all the files that meet the word in that new folder.
So for example, if i have a folder named "files_1906" and inside i have a file named "hello_1906.pdf" and then another folder (not inside the first) named "hello" and inside a file named "1906hello" and if i introduce: .sh program_name.sh "1906" it should create a folder named 1906 and copy the two files inside.
but right now i only managed to create the folders, it doesn't copy the files.
here is the full code:
#!/bin/bash
currentDir=$(pwd)
for var in "$@"
do
countCurrentWord=$(find . -type f -name "$var" | grep "$var" | wc -l)
echo -e "$countCurrentWord"
echo -e "Number of files matching the keyword $var: $countCurrentWord"
if [[ $countCurrentWord -gt 0 ]]
then
if [[ -d $currentDir/$var ]]
then
echo -e "the folder $var already exists, copying the files...\n"
else
mkdir "$currentDir"/"$var"
echo -e "the folder $var does not exist, creating the folder and copying the files...\n"
fi
IFS=$'\n'
array=$(find . -type f -name "$var")
declare -p array > /dev/null 2>&1
declare -a array > /dev/null 2>&1
length=${#array[@]}
for ((i=0; i < length; i ))
do
cp "${array[i]}" "$currentDir"/"$var"/ > /dev/null 2>&1
echo -e "hello world"
done
else
echo -e "no files found, no move will be made.\n"
fi
done
the console returns the following:
first it says that $countCurrentWord = 0, which doesn't make any sense cause there are files that contains "1906"
but here is where this get's trippy:
the program ignores the greater than 0 and starts the loop, creating the folder called 1906, but after, it doesn't copy the file, it remains empty once is created. I get also the hello world from the second loop.
So, how can we fix this so that it does its job and copies the files?
i already tried with files named only "1906" (also does not work), but the script should be capable of reading the full name of a file and discern if contains the keyword introduced. i don't know if i have a typo or anything, i'm not experienced in scripting in bash
I hope i explained this well enough so you guys can help me, thank you for your effort!
CodePudding user response:
Full rewrite: Running find
only once for each submited words and use reliable bash variable for storing found file names.
#!/bin/bash
shopt -s extglob
currentDir=$(pwd)
for var ;do
mapfile -d '' -t FoundFiles < <(
find !($var) -type f -iname "*$var*" -print0
)
if (( ${#FoundFiles[@]} > 0 )) ;then
if [[ -d $currentDir/$var ]] ;then
printf 'Folder %s already exist.\n' "$var"
else
printf 'Create new folder '%s'.\n' "$var"
mkdir "$currentDir/$var"
fi
cp -at "$currentDir/$var" "${FoundFiles[@]}"
else
printf 'No file found with \47%s\47.\n' "$var"
fi
done
- Avoid
find
to browse folder named$var
, by usingshopt -s extglob
andfind !($var)
. - In 4th line,
for var; do
, the common casein "$@"
is implicit. cp -ait "/path/to/target" "${array[@]}"
is reliable, work with special file names and so on.
I didn't add echo "hello world"
command, but feel free to add, maybe a v
flag to cp
command:
cp -avt "$currentDir/$var" "${FoundFiles[@]}"
... And/or a i
flag to ask before overwrite:
cp -viat "$currentDir/$var" "${FoundFiles[@]}"
CodePudding user response:
1) You're using the $var
string as a literal with mkdir
, as a filename glob with find
and as an extended regex with grep
. The characters can have different meanings in each of these contexts.
Given your end-goal, the grep
can be dropped, so you just have to make sure that $var
keeps a literal meaning when used in a glob. Here's a way to do the glob-escaping with awk
:
#!/bin/bash
var='* [string] ?'
IFS='' read -r -d '' var_glob_safe < <(
LANG=C awk '
BEGIN {
gsub(/[\\?*[]/,"\\\\&", ARGV[1])
printf "%s%c", ARGV[1], 0
}
' "$var"
)
printf '%s\n' "$var_glob_safe"
\* \[string] \?
note: the only valid delimiter for arbitrary paths is the NUL
byte.
Well, that's the gist of it; there's no good reason to fire an awk
for each $var
in $@
because you can do all the glob-escapes in one fell swoop and read them one by one while iterating over $@
:
#!/bin/bash
for var in "$@"
do
IFS='' read -r -d '' var_glob_safe
# ...
done < <(
LANG=C awk '
BEGIN {
for (i = 1; i < ARGC; i ) {
gsub(/[\\?*[]/,"\\\\&", ARGV[i])
printf "%s%c", ARGV[i], 0
}
}
' "$@"
)
2) For finding the files that contain $var_glob_safe
in their name, the command would be:
find . -type f -name "*$var_glob_safe*"
But you want to fill a bash array with the result, so you have to tell find
to use a NUL
byte delimiter (with -print0
) and populate the array like this:
# for bash >= 4.3
readarray -t -d '' files < <(
find . -type f -name "*$var_glob_safe*" -print0
)
# for bash < 4.3
files=()
while IFS='' read -r -d '' f
do
files =( "$f" )
done <(
find . -type f -name "*$var_glob_safe*" -print0
)
Now you have access to the total number of "files" with ${#files[@]}
, so you can base your logic on that.
3) Here's a code that incorporates the above explanations and more; the only missing parts are the echo
s:
#!/bin/bash
for var in "$@"
do
[[ $var == */* ]] && exit 1 # a filename can't contain a slash
IFS='' read -r -d '' var_glob_safe
readarray -d '' files < <(
find . -type f -name "*$var_glob_safe*" -print0
)
if (( ${#files[@]} ))
then
mkdir -p "$var" || exit 1
cp "${files[@]}" "$var"
fi
done < <(
awk '
BEGIN {
for (i = 1; i < ARGC; i ) {
gsub(/[\\?*[]/,"\\\\&", ARGV[i])
printf "%s%c", ARGV[i], 0
}
}
' "$@"
)