I am currently attempting to create a bash script that will check inside of each users /Library/Mail folder to see if a folder named V2 exists. The script should create an array with each item in the array being a user and then iterate through each of these users checking their home folder for the above captioned contents. This is what I have so far:
#!/bin/bash
cd /Users
array=($(ls))
for i in ${array[#]}
do
if [ -d /$i/Library/Mail/V2 ]
then
echo "$i mail has been upgraded."
else
echo "$i FAIL"
fi
done
Populating your array from the output of ls is going to make for serious problems when you have a username with spaces. Use a glob expression instead. Also, using [ -d $i/... ] will similarly break on names with spaces -- either use [[ -d $i/... ]] (the [[ ]] construct has its own syntax rules and doesn't require quoting) or [ -d "$i/..." ] (with the quotes).
Similarly, you need to double-quote "${array[#]}" to avoid string-splitting from splitting names with spaces in two, as follows:
cd /Users
array=(*)
for i in "${array[#]}"; do
if [[ -d $i/Library/Mail/V2 ]]; then
echo "$i mail has been upgraded."
else
echo "$i FAIL"
fi
done
That said, you don't really need an array here at all:
for i in *; do
...check for $i/Library/Mail/V2...
done
Related
I have a problem which is iterating a file called for example: fileAndFolderPaths, and in other script I have to iterate this same file and check if each line is a file or folder path.
fileAndFolderPaths
/opt/sampleFolder
/opt/sampleFolder/aText.txt
/opt/otherFolder
Then my script file is something like that:
myScript.sh
#!/bin/bash
mapfile -t array < /tmp/fileAndFolderPaths
function checkIfFilesOrFolder(){
for i in "${array[#]}" do
if [ -f $i ]; then
echo -e "[Info] found the file: $i"
elif [ -d $i ]; then
echo -e "[Info] found the directory: $i"
else
echo -e "[Error] Nor directory or file were found based on this value: $i"
fi
done
}
checkIfFilesOrFolder
exit 0;
The problem is the check only works for the last line of the array created by the mapfile command. Any thoughts about that? I'm new to shell scripting so probably this is a really basic problem, but even so I wasn't able to fix it yet.
A couple of review suggestions, if you don't mind:
Don't need the global variable: pass the filename to the function and loop over the file:
checkIfFilesOrFolder() {
local file=$1
while IFS= read -r line; do
# test "$line" here ...
done < "$file"
}
checkIfFilesOrFolder /tmp/fileAndFolderPaths
I recommend using local for function variables, to minimize polluting the global namespace.
Always quote your variables, unless you're aware of exactly what expansions occur on them unqoted:
if [ -f "$line" ]; then ...
is there a reason you're using echo -e? The common advice is to use
printf '[Info] found the file: %s\n' "$line"
Interesting reading: Why is printf better than
echo?
I want to create a script that loops over multiple directories from an array and, if the files there, which are not in the blacklist, are older than a certain time period, remove them. The problem is that any type of string comparison (whether grep -q or wildcards) doesn't work when trying to list a directory with files that contain spaces in them (so I change the $IFS value to loop through them), making the script unusable. Blacklisted strings can also have spaces in them, of course.
Here's what I wrote so far:
#!/bin/bash
declare -a dirs=(~/path/to/dir1/* ~/path/to/dir2/*)
declare -a blacklist=("file number 1" "file number 2" "file number 3")
saveifs=$IFS
IFS=$'\n'
echo "Starting the autocleaner..."
for dirname in "${dirs[#]}"; do
for filename in $(ls "$dirname"); do
for excluded in ${blacklist[#]}; do
if [ -e $filename ]; then
if echo "$filename" | grep -q "$excluded"; then
# if [[ "$filename" == *"$excluded"* ]]; then
:
else
if test `find "$filename" -mtime +1`; then
# rm -f $filename
echo "File $filename removed."
else
echo "File $filename is up-to-date and doesn't need to be removed."
fi
fi
else
:
fi
done
done
done
IFS=$saveifs
How can I make the comparison actually work?
Have you tried using single square brackets [ ... ] for the comparison line? Reading about the difference here between [ ... ] and [[ ... ]] may help you.
Ultimately, I want to get rid of the possibility of duplicate entries showing up my array. The reason I'm doing this is because I'm working on a script that compares two directories, searches for, and deletes duplicate files. The potential duplicate files are stored in an array and the files are only deleted if they have the same name and checksum as the originals. So if there are duplicate entries, I wind up encountering minor errors where md5 either tries to find the checksum of a file that doesn't exist (because it was already deleted) or rm tries to delete a file that was deleted already.
Here's part of the script.
compare()
{
read -p "Please enter two directories: " dir1 dir2
if [[ -d "$dir1" && -d "$dir2" ]]; then
echo "Searching through $dir2 for duplicates of files in $dir1..."
else
echo "Invalid entry. Please enter valid directories." >&2
exit 1
fi
#create list of files in specified directory
while read -d $'\0' file; do
test_arr+=("$file")
done < <(find $dir1 -print0)
#search for all duplicate files in the home directory
#by name
#find checksum of files in specified directory
tmpfile=$(mktemp -p $dir1 del_logXXXXX.txt)
for i in "${test_arr[#]}"; do
Name=$(sed 's/[][?*]/\\&/g' <<< "$i")
if [[ $(find $dir2 -name "${Name##*/}" ! -wholename "$Name") ]]; then
[[ -f $i ]] || continue
find $dir2 -name "${Name##*/}" ! -wholename "$Name" >> $tmpfile
origray[$i]=$(md5sum "$i" | cut -c 1-32)
fi
done
#create list of duplicate file locations.
dupe_loc
#compare similarly named files by checksum and delete duplicates
local count=0
for i in "${!indexray[#]}"; do
poten=$(md5sum "${indexray[$i]}" | cut -c 1-32)
for i in "${!origray[#]}"; do
if [[ "$poten" = "${origray[$i]}" ]]; then
echo "${indexray[$count]} is a duplicate of a file in $dir1."
rm -v "${indexray[$count]}"
break
fi
done
count=$((count+1))
done
exit 0
}
dupe_loc is the following function.
dupe_loc()
{
if [[ -s $tmpfile ]]; then
mapfile -t indexray < $tmpfile
else
echo "No duplicates were found."
exit 0
fi
}
I figure the best way to solve this issue would be to use the sort and uniq commands to dispose of duplicate entries in the array. But even with process substitution, I encounter errors when trying to do that.
First things first. Bash array sorting has been answered here: How to sort an array in BASH
That said, I don't know that sorting the array will be much help. It seems a more simple solution would just be wrapping your md5 check and rm statements in an if statement:
if [ -f origarr[$i]} ]; do #True if file exists and is a regular file.
#file exists
...
rm ${origarr[$i]}
fi
I'm trying to write a script that performs actions of files with different extensions. To make it as easy to add different actions as possible, the extensions are read from an array, files are found via the "find" command, and results returned to a dynamically generated array named after the file extension that was searched.
To add a new extension to search for I can simply add to the file_ext array.
I create the array like this:
file_exts=("dsd" "dsdd" "dmesg")
for ext in "${file_exts[#]}"
do
echo "Finding $ext files"
eval var="exts_$ext"
declare -a $var="($( find "$dirs_target" -name "*.$ext"))"
done
The arrays are created correctly, and I can manually echo "${exts_dsd[0]} ${exts_dsd[1]}" and see the entries, However, I can't find a way of looping through each entry in the dynamically assigned arrays.
I have tried a few combinations using eval, and I can print out the first entry in the array, IE just referencing "$exts_dsd" Here are two things I've already tried:
for varname in "${!exts_#}"
do
for entry in ${varname[#]}
do
echo "$varname : $entry"
done
eval value=\$${varname[#]}
echo "$varname=$value"
done
How can I loop through each entry in the above for loop, so I can print out all the entries in all the dynamically created arrays?
Here is a complete test script:
#! /bin/bash
file_exts=("dsd" "dsdd" "dmesg")
dirs_target="/tmp/arraytest/"
echo "Creating $dirs_target"
if [[ ! -d "$dirs_target" ]]; then
if ! mkdir "$dirs_target"; then
echo "Couldn't create temp dir"
exit 1
fi
fi
echo "Creating test files"
for tmpfile in $( seq 0 5 )
do
echo -e "\tCreating $dirs_target$tmpfile.dsd"
if ! touch "$dirs_target/$tmpfile.dsd"; then
echo "Coudn't create $dirs_target/test$tmpfile.dsd"
exit 1
fi
done
echo ""
echo "-----Finding Files-----"
for ext in "${file_exts[#]}"
do
echo "Finding $ext files"
eval var="exts_$ext"
declare -a $var="($( find "$dirs_target" -name "*.$ext"))"
done
echo ""
echo "-----File Extensions-----"
for varname in "${!exts_#}"
do
for entry in ${varname[#]}
do
echo "$varname : $entry"
done
eval value=\$${varname[#]}
#echo "$varname=$value"
done
echo ""
echo "Finishing."
rm -rf "$dirs_target"
To loop over the entries, you have to use the same trick as when creating them: just store the variable name in a variable. The point is to include the [#] index, too, which will be correctly recognized in the indirection:
for varname in "${!exts_#}" ; do
arr=$varname'[#]'
for entry in "${!arr}" ; do
echo "$varname : $entry"
done
done
Also note that eval isn't needed in
# eval var="exts_$ext"
var=exts_$ext # works even better!
I've found the answer. I had the eval statement slightly wrong.
echo "-----File Extensions-----"
for varname in "${!exts_#}"
do
echo "varname=$varname"
eval testvalue="\${$varname[#]}"
for entry in $testvalue
do
echo -e "\tFile: $entry"
done
done
As a bonus, I've also figured out how to add to a dynamically created array
var="table_$entry"
declare -a $var
while read -r line
do
eval $var+=\(\'"$line"\'\)
done < "$dirs_table"
Let's say I have this directory structure:
DIRECTORY:
.........a
.........b
.........c
.........d
What I want to do is: I want to store elements of a directory in an array
something like : array = ls /home/user/DIRECTORY
so that array[0] contains name of first file (that is 'a')
array[1] == 'b' etc.
Thanks for help
You can't simply do array = ls /home/user/DIRECTORY, because - even with proper syntax - it wouldn't give you an array, but a string that you would have to parse, and Parsing ls is punishable by law. You can, however, use built-in Bash constructs to achieve what you want :
#!/usr/bin/env bash
readonly YOUR_DIR="/home/daniel"
if [[ ! -d $YOUR_DIR ]]; then
echo >&2 "$YOUR_DIR does not exist or is not a directory"
exit 1
fi
OLD_PWD=$PWD
cd "$YOUR_DIR"
i=0
for file in *
do
if [[ -f $file ]]; then
array[$i]=$file
i=$(($i+1))
fi
done
cd "$OLD_PWD"
exit 0
This small script saves the names of all the regular files (which means no directories, links, sockets, and such) that can be found in $YOUR_DIR to the array called array.
Hope this helps.
Option 1, a manual loop:
dirtolist=/home/user/DIRECTORY
shopt -s nullglob # In case there aren't any files
contentsarray=()
for filepath in "$dirtolist"/*; do
contentsarray+=("$(basename "$filepath")")
done
shopt -u nullglob # Optional, restore default behavior for unmatched file globs
Option 2, using bash array trickery:
dirtolist=/home/user/DIRECTORY
shopt -s nullglob
contentspaths=("$dirtolist"/*) # This makes an array of paths to the files
contentsarray=("${contentpaths[#]##*/}") # This strips off the path portions, leaving just the filenames
shopt -u nullglob # Optional, restore default behavior for unmatched file globs
array=($(ls /home/user/DIRECTORY))
Then
echo ${array[0]}
will equal to the first file in that directory.