shell script to check for multiple files with different names - bash

I am creating a script to check for 3 files in Directory, take the count of rows in them and mail if the rows exist.I have to send only one mail if either of these files have count, I am ending up sending 3 mails.
For Ex. I have these files
process_date.txt
thread_date.txt
child_date.txt
Iam doing something like
$1= process_date.txt
$2= thread_date.txt
$3= child_date.txt
if [ -f $1 ]
then
count1=`wc-l < $1`
if $count1 -ne 0 then mailx abc.com
fi
fi
if [ -f $2 ]
then
count2=`wc-l < $2`
if $count2 -ne 0 then mailx abc.com
fi
fi
if [ -f $3 ]
then
count3=`wc-l < $3`
if $count3 -ne 0 then mailx abc.com
fi
fi

As you stated your question, it seems you only need to check whether at least one of the files is non-empty: you don't need to count the number of rows. In Bash, you may use the [[ -s file ]] test to exactly test whether file exists and is non-empty. So you can do:
#!/bin/bash
if [[ -s $1 ]] || [[ -s $2 ]] || [[ -s $3 ]]; then
mailx abc.com
fi
More generally, you can have the mail sent if at least one of the files given as arguments exists and is non-empty:
#!/bin/bash
for file; do
if [[ -s $file ]]; then
mailx abc.com
break
fi
done
You'll call this as
scriptname process_date.txt thread_date.txt child_date.txt

You can wrap your script in a function and use return command after every mailx, like this:
send_one_mail() {
if [ -f "$1" ]
then
count1=$(wc -l < "$1")
if [ $count1 -ne 0 ]
then
mailx abc.com
return
fi
fi
# etc. for other conditions
}
send_one_mail process_date.txt thread_date.txt child_date.txt

Try this:
if [ -f $1 ]
then
count1=`wc -l < $1`
fi
if [ -f $2 ]
then
count2=`wc -l < $2`
fi
if [ -f $3 ]
then
count3=`wc -l < $3`
fi
if [ $count1 -ne 0 -o $count2 -ne 0 -o $count3 -ne 0 ]
then
mailx abc.com
fi

Related

Strange syntax error in if condition - Shell Script

So I'm making a shell script in Ubuntu. It's purpose is simple. You give a command with arguments and you get a different operation each time. The problem is that when I run the the script it won't actually run because of a syntax error in one elif. The most suspicious thing is that I have a similar elif above wich works or at least doesn't pop a syntax error...
I'm leaving my code for you to see it and understand. Thanks in advance!
if [ "$1" = "-a" -a $# -lt 3 ]
then
echo "Add a new line in katalogos!"
read -p "Give me a name... " name
read -p "Give me a surname... " surname
read -p "Give me a city name... " cityName
read -p "Give me a phone number... " num
echo "$name $surname $cityName $num" > katalogos
elif [ "$1" = "-l" -a $# -lt 3 ]
then
echo "Content of katalogos will be sorted numerically and blank lines will be excluded!"
sort -b -n katalogos
elif [ "$1" = "-s" -a $# -lt 4 ]
if [[ $2 != *[!0-9]* ]]
then
echo "Content of katalogos will be sorted according to the second argument!"
sort +$3 katalogos
fi
elif [ "$1" = "-c" -a $# -lt 4 ] // syntax error
if [[ $2 = *[!0-9]* ]]
then
echo "Content of katalogos will be sorted according to the keyword!"
if [ $(grep -e "$2" katalogos | wc -l) -eq 0 ]
then
echo "String is not matched."
else
grep -e "$2" katalogos
fi
fi
elif [ "$1" = "-d" -a ( "$3" = "-b" -o "$3" = "-r" ) ]
if [[ $2 = *[!0-9]* ]]
then
echo "Katalogos's string matching lines will be deleted and blank lines will be in their place, assuming that the third argument equals -b, else just the lines will be deleted!"
if [ $(grep -e $2 katalogos | wc -l) -eq 0 ]
then
echo "String is not matched."
else
if [ "$3" = "-b" ]
then
sed -i "$3" katalogos | sed -i '$ a '
echo "A blank line inserted in place of the deleted one."
else
sed -i "$3" katalogos
echo "Line deleted."
fi
fi
fi
elif [ "$1" = "-n" ]
echo "katalogos's number of blank lines will be shown with the ability to delete them!"
grep -cvP '\S' katalogos
read -p "Do you want to delete them? Type 1 for yes or 0 for no... " ans
if [ $ans -eq 1 ]
then
grep -cvP '\S' file | sed -i
echo "Lines deleted."
fi
else
echo "Help centre!"
echo "-Type ./telcat -a to insert a new line to katalogos."
echo "-Type ./telcat -l to see the contents of katalogos sorted numerically (excluding blank lines)."
echo "-Type ./telcat -s plus a number to see the contents of katalogos sorted by the data that the number points to."
echo "-Type ./telcat -c plus a keyword to see only the lines that match with the word given."
echo "-Type ./telcat -d plus a keyword and -b or -r to delete the lines that contain the word given. Specifically if the third argument is -b it will automatically add a blank line to the deleted one and if it is -r it will not."
echo "-Type ./telcat -n to see the number of blank lines of katalogos."
echo "End of help centre!"
fi

UNIX terminal - shell won't terminate, stuck in for loop

I'm having some trouble terminating a shell. I'm starting several filkontroll.sh in the background to regularly check files to see if they have been changed or deleted. It seems to be running fine except that when all the files have been modified, the program won't terminate. It's seems to be stuck in the for-loop somehow.
filkontroll.sh :
#!/bin/bash
clear
declare -i status=1
if [ -f $1 ]
then
status=0
timestamp=$(stat -f "%Sm" -t "%H%M%S" $1)
fi
while [ 0 ]
do
if [ -f $1 ] && [ $status -eq 1 ]
then
echo "Filen $1 ble opprettet."
break
elif [ ! -f $1 ] && [ $status -eq 0 ]
then
echo "Filen $1 ble slettet."
break
elif [ -f $1 ]
then
sistEndret=$(stat -f "%Sm" -t "%H%M%S" $1)
if [ ! $sistEndret -eq $timestamp ]
then
echo "Filen $1 ble endret."
break
fi
fi
sleep $2
done
kontrollflerefiler.sh:
#!/bin/bash
clear
for fil in $#
do
. filkontroll.sh $fil 2 &
done

Bash script - Check if a file exists

I need to check if a file with a specific extension exists and if it is not empty.
The problem is that I don't know the name of the file, just the extension and the path.
Here my solution with an known name
FILE="/Users/test/my.out"
if [[ -f $FILE && -s $FILE ]] ; then echo "EXIST"; fi
Using
FILE="/Users/test/*.out"
Do not work
I suggest you use an array, in case there are multiple files:
arr=( /Users/test/*.out )
if (( ${#arr[#]} > 0 )) && [[ -s "${arr[0]}" ]]
then
...
fi
You can either use #dogbane's solution using an array or use a loop:
dir=/users/test
for file in "${dir}"/*.out; do
break
done
if [ -f "${file}" ] && [ -s "${file}" ]; then
echo "found a regular, non-empty .out file: ${file}"
fi
I think this should do:
if [ -f ${var} ]; then
if [ -s ${var} ]; then
echo "Found $var"
fi
fi
Try
FILE=$(ls "Downloads/*.zip" 2>/dev/null)
Only works, if there is only a single file.

Autocomplete file name in a bash script

I'm trying to imitate the bash file completion.
Suppose I have the following files:
test1
test2
With an input string of "te" I would like to get the output "test"
This is my current attempt ($c is the input string):
l=1
q="$c"
for j in $(ls -A | grep "^$c"); do
if [ "${j/$c}" != "$j" ]; then
n=$(ls -A | grep ^$j | wc -l)
if [ $n -gt $l ]; then
q="$j"
fi
fi
done
c="$q"
echo $c
Thanks for any help
I tend to think there is no a way to get this from completion engine since it’s not a part of GNU Bash but Readline. But at least we can get list of possible completions with compgen. And an inmplementaion of finding longest common prefix should not be problem. So...
#!/bin/bash
SCRIPTNAME="${0##*/}"
USAGE="Usage: $SCRIPTNAME <prefix>
Print common prefix of possible file name completions. Like <TAB> but to
stdout."
(( $# == 1 )) || { printf >&2 '%s\n' "$USAGE"; exit 1; }
PREFIX="$1"
commonprefix() {
(( $# >= 2 )) || {
echo "$1"
return 0
}
local -i i N M
for ((i=0; i<=${#1}; i++)); do
for ((N=1; N<=$#-1; N++)); do
let M=$N+1
[[ ${!N:i:1} == ${!M:i:1} ]] || break 2
done
done
echo "${1:0:i}"
}
readarray -t COMPLETIONS < <(compgen -f "$PREFIX")
commonprefix "${COMPLETIONS[#]}"
Although Dmitry Alexandrov already provided a better solution, I still would like to post my own one which I made while waiting for the answers:
l=1
while [ -n $l ]; do
l=${#c}
a=$(ls -A | grep "^$c" | wc -l)
q=$c
for i in $(ls -A | grep "^$q"); do
if [ $i == $q ]; then
unset l
break
else
v=$(ls -A | grep "^$q${i:$l:1}" | wc -l)
if [ $v == $a ]; then
q="$c${i:$l:1}"
break
fi
fi
done
if [ $c == $q ]; then break; fi
c=$q
done
echo $c
It works with all of my tests, but it's slow (although it could be optimized).
Just to show that you were thinking in correct direction, I made your code work:
#!/bin/bash
c=$1
q="$c"
for j in $c*; do
if [ "${j/$c}" != "$j" ]; then
startn=$(ls -1A | grep -c "^${j:0:$((${#c} + 1))}")
for (( i=${#c}; i <= ${#j}; i++ )); do
n=$(ls -1A | grep -c "^${j:0:$i}")
if [ "$n" -lt "$startn" ]; then
q="${j:0:$((i - 1))}"
elif [ "$n" -le "$startn" ]; then
q="${j:0:$i}"
fi
done
fi
done
c="$q"
echo "$c"
But, it's just a proof of concept, don't use it. See answer by Dmitry Alexandrov for a good solution.

Test multiple file conditions in one swoop (BASH)?

Often when writing for the bash shell, one needs to test if a file (or Directory) exists (or doesn't exist) and take appropriate action. Most common amongst these test are...
-e - file exists, -f - file is a regular file (not a directory or device file), -s - file is not zero size, -d - file is a directory, -r - file has read permission, -w - file has write, or -x execute permission (for the user running the test)
This is easily confirmed as demonstrated on this user-writable directory....
#/bin/bash
if [ -f "/Library/Application Support" ]; then
echo 'YES SIR -f is fine'
else echo 'no -f for you'
fi
if [ -w "/Library/Application Support" ]; then
echo 'YES SIR -w is fine'
else echo 'no -w for you'
fi
if [ -d "/Library/Application Support" ]; then
echo 'YES SIR -d is fine'
else echo 'no -d for you'
fi
➝ no -f for you ✓
➝ YES SIR -w is fine ✓
➝ YES SIR -d is fine ✓
My question, although seemingly obvious, and unlikely to be impossible - is how to simply combine these tests, without having to perform them separately for each condition... Unfortunately...
if [ -wd "/Library/Application Support" ]
▶ -wd: unary operator expected
if [ -w | -d "/Library/Application Support" ]
▶ [: missing `]'
▶ -d: command not found
if [ -w [ -d "/Library.... ]] & if [ -w && -d "/Library.... ]
▶ [: missing `]'
➝ no -wd for you ✖
➝ no -w | -d for you ✖
➝ no [ -w [ -d .. ]] for you ✖
➝ no -w && -d for you ✖
What am I missing here?
You can use logical operators to multiple conditions, e.g. -a for AND:
MYFILE=/tmp/data.bin
if [ -f "$MYFILE" -a -r "$MYFILE" -a -w "$MYFILE" ]; then
#do stuff
fi
unset MYFILE
Of course, you need to use AND somehow as Kerrek(+1) and Ben(+1) pointed it out. You can do in in few different ways. Here is an ala-microbenchmark results for few methods:
Most portable and readable way:
$ time for i in $(seq 100000); do [ 1 = 1 ] && [ 2 = 2 ] && [ 3 = 3 ]; done
real 0m2.583s
still portable, less readable, faster:
$ time for i in $(seq 100000); do [ 1 = 1 -a 2 = 2 -a 3 = 3 ]; done
real 0m1.681s
bashism, but readable and faster
$ time for i in $(seq 100000); do [[ 1 = 1 ]] && [[ 2 = 2 ]] && [[ 3 = 3 ]]; done
real 0m1.285s
bashism, but quite readable, and fastest.
$ time for i in $(seq 100000); do [[ 1 = 1 && 2 = 2 && 3 = 3 ]]; done
real 0m0.934s
Note, that in bash, "[" is a builtin, so bash is using internal command not a symlink to /usr/bin/test exacutable. The "[[" is a bash keyword. So the slowest possible way will be:
time for i in $(seq 100000); do /usr/bin/\[ 1 = 1 ] && /usr/bin/\[ 2 = 2 ] && /usr/bin/\[ 3 = 3 ]; done
real 14m8.678s
You want -a as in -f foo -a -d foo (actually that test would be false, but you get the idea).
You were close with & you just needed && as in [ -f foo ] && [ -d foo ] although that runs multiple commands rather than one.
Here is a manual page for test which is the command that [ is a link to. Modern implementations of test have a lot more features (along with the shell-builtin version [[ which is documented in your shell's manpage).
check-file(){
while [[ ${#} -gt 0 ]]; do
case $1 in
fxrsw) [[ -f "$2" && -x "$2" && -r "$2" && -s "$2" && -w "$2" ]] || return 1 ;;
fxrs) [[ -f "$2" && -x "$2" && -r "$2" && -s "$2" ]] || return 1 ;;
fxr) [[ -f "$2" && -x "$2" && -r "$2" ]] || return 1 ;;
fr) [[ -f "$2" && -r "$2" ]] || return 1 ;;
fx) [[ -f "$2" && -x "$2" ]] || return 1 ;;
fe) [[ -f "$2" && -e "$2" ]] || return 1 ;;
hf) [[ -h "$2" && -f "$2" ]] || return 1 ;;
*) [[ -e "$1" ]] || return 1 ;;
esac
shift
done
}
check-file fxr "/path/file" && echo "is valid"
check-file hf "/path/folder/symlink" || { echo "Fatal error cant validate symlink"; exit 1; }
check-file fe "file.txt" || touch "file.txt" && ln -s "${HOME}/file.txt" "/docs/file.txt" && check-file hf "/docs/file.txt" || exit 1
if check-file fxrsw "${HOME}"; then
echo "Your home is your home from the looks of it."
else
echo "You infected your own home."
fi
Why not write a function to do it?
check_file () {
local FLAGS=$1
local PATH=$2
if [ -z "$PATH" ] ; then
if [ -z "$FLAGS" ] ; then
echo "check_file: must specify at least a path" >&2
exit 1
fi
PATH=$FLAGS
FLAGS=-e
fi
FLAGS=${FLAGS#-}
while [ -n "$FLAGS" ] ; do
local FLAG=`printf "%c" "$FLAGS"`
if [ ! -$FLAG $PATH ] ; then false; return; fi
FLAGS=${FLAGS#?}
done
true
}
Then just use it like:
for path in / /etc /etc/passwd /bin/bash
{
if check_file -dx $path ; then
echo "$path is a directory and executable"
else
echo "$path is not a directory or not executable"
fi
}
And you should get:
/ is a directory and executable
/etc is a directory and executable
/etc/passwd is not a directory or not executable
/bin/bash is not a directory or not executable
This seems to work (notice the double brackets):
#!/bin/bash
if [[ -fwd "/Library/Application Support" ]]
then
echo 'YES SIR -f -w -d are fine'
else
echo 'no -f or -w or -d for you'
fi

Resources