Errors with a shell-script - bash

i found some freaky error. I want to increment a counter, but the variable isnt visible outside the while do.
The script as follows:
## $1 - The file which should be examined
## $2 - The time passed between the checks. If $2 is 5 then all lines from the last 5 minutes are taken
## $3 - The Errormessage to search for
outputOK="OK - nothing happened"
output_logcheck=0;
errlines="";
cat $1 | grep "$3" | while read line
do
linedate=`date -d "$(echo $line | cut -d " " -f 2)" '+%s'`
nowdate=`date '+%s'`
if [ $(( $nowdate - (60 * $2) )) -le $linedate ]
then
$output_logcheck=$[$output_logcheck+1]
$errlines="${errlines} -- ${line}"
fi
done;
if [ $output_logcheck -eq 0 ]
then
echo $outputOK
else
echo "CRITICAL - There are -= ${output_logcheck} =- $3 -- Lines: $errlines"
fi
So i dont know what else to try.
Thanks in advance.

The problem is that pipe create a SubShell.
change
cat $1 | grep "$3" | while read line
do
...
done
to
while read line
do
...
done <(cat $1 | grep "$3")

As noted, the Bash shell, creates a subshell whenever a pipe is opened to a loop. In that case, variables within the loop are local to the loop.
One kludge is to substitute (if possible) a Korn ('ksh') shell for the Bash one.

Try something like:
## $1 - The file which should be examined
## $2 - The time passed between the checks. If $2 is 5 then all lines from the last 5 minutes are taken
## $3 - The Errormessage to search for
outputOK="OK - nothing happened"
outfile="/tmp/result.$$"
trap { rm $outfile } 0 1 2 3
cat $1 | grep "$3" | (output_logcheck=0; errlines=""; while read line
do
linedate=`date -d "$(echo $line | cut -d " " -f 2)" '+%s'`
nowdate=`date '+%s'`
if [ $(( $nowdate - (60 * $2) )) -le $linedate ]
then
$output_logcheck=$[$output_logcheck+1]
$errlines="${errlines} -- ${line}"
fi
done; echo $output_logcheck ":" $errlines > $outfile)
output_logcheck=`cat $outfile| cut -f1 -d:`
errlines=`cat $outfile|cut -f2 -d:`
if [ $output_logcheck -eq 0 ]
then
echo $outputOK
else
echo "CRITICAL - There are -= ${output_logcheck} =- $3 -- Lines: $errlines"
fi

while is executed in a separate process. Variables that are changed in the context of that process still hold their unchanged valus in the parent process.

Related

String comparison from nested for returns always false

The main issue is that i try to parse ls to do a mock "Compare directories" but when i do so since i use nested fors i cant properly compare the results from it since the comparison of two filenames/strings even if they are the same it always returns false
I tried erasing the white characters but no results.
var1=$(ls -l $1 | grep -v ^d | tail -n +2 | tr -s " "| cut -d " " -f 9)
var2=$(ls -l $2 | grep -v ^d | tail -n +2 | tr -s " "| cut -d " " -f 9)
for i in $var1 ; do
i=$(printf "$i" | tr -d '[:space:]')
flag=0
var3=$(ls -l $1 | grep -v ^d | tail -n +2 | tr -s " " | grep $i | cut -d " " -f 5)
for j in $var2 ; do
j=$(printf $j | tr -d '[:space:]')
var4=$(ls -l $2 | grep -v ^d | tail -n +2 | tr -s " " | grep $j | cut -d " " -f 5)
if [ "$i" == "$j" ] ; then
if [ "$var3" != "$var4" ] ; then
flag=1
fi
else
flag=1
fi
done
if [ $flag -eq 1 ] ; then
printf "$i file does not exist on the $2 catalog\n"
printf "It 's size is :$var3 \n"
let Sum=$Sum+$var3
fi
done
This is not a string comparison problem, it's a logic problem.
I wrote you a MCVE that demonstrates the same problem with less code and fewer dependencies:
flag=0
target="hello"
for candidate in "hello" "world"
do
if [ "$target" != "$candidate" ]
then
flag=1
fi
done
if [ "$flag" -eq 1 ]
then
echo "The string was not found"
fi
This prints The string was not found every time, just like your script, even though it's clearly there.
The problem here is that the script requires that ALL files match. It should only require that ANY file matches. The easiest way to fix this is to:
Set flag=1 when a MATCH is found (not a mismatch)
Make flag=1 signify that a match was found (rather than no match was found)
Here's the version which correctly finds the string:
flag=0
target="hello"
for candidate in "hello" "world"
do
if [ "$target" = "$candidate" ]
then
flag=1
fi
done
if [ "$flag" -eq 1 ]
then
echo "The string was found"
else
echo "The string was not found"
fi

Redirection based on variables assigned within while loop setup

I want to access while loop variable out side the loop
while read line
do
...
...
...
done < $file > /home/Logs/Sample_$line_$(date +"%Y%m%d_%H%M%S").log
In the above example whatever the log file is getting generated that doesn't have the value for the line variable. i.e. $line is not working here.
Please let me know how this can be written to make it work.
#!/bin/sh
exec 1> /home/Logs/`basename $0 | cut -d"." -f1 | sed 's/\.sh//g'`_$(date +"%Y%m%d_%H%M%S").log 2>&1
echo "Execution Started : `date` \n"
SQL_DIR=/home/Sql
INFILE=in_file
TEMPFILE=temp_file
RETURN_CODE=0
ls -ltr $SQL_DIR|grep ".sql"|awk -F" " '{print $9}'|awk -F"." '{print $1}' > $INFILE
sed '/^$/d' $INFILE > $TEMPFILE; mv $TEMPFILE $INFILE
while read line
do
{
START_TIME=`date +%s`
printf "\n SQL File Executed Is : $line.sql"
SQL_FILE_NM=$line.sql
SQL_FILE=$SQL_DIR/$SQL_FILE_NM
nzsql -db netezza_db -Atqf $SQL_FILE > /dev/null
RETURN_CODE=$?
if [ $RETURN_CODE -eq 0 ]; then
echo "Time taken to execute sqlfile $SQL_FILE=$TT_HRS:$TT_MINS:$TT_REM_SECS HH:MM:SS" > $TEMPFILE
printf "\n Success: Sql completed successfully at `date` \n"
cat $TEMPFILE|mailx -s "Time taken to execute sqlfile $SQL_FILE=$TT_HRS:$TT_MINS:$TT_REM_SECS HH:MM:SS" 'koushik.chandra#a.com'
else
printf "\n Error: Failed in sql execution at `date`"
exit $RETURN_CODE
fi
END_TIME=`date +%s`
TT_SECS=$(( END_TIME - START_TIME))
TT_HRS=$(( TT_SECS / 3600 ))
TT_REM_MS=$(( TT_SECS % 3600 ))
TT_MINS=$(( TT_REM_MS / 60 ))
TT_REM_SECS=$(( TT_REM_MS % 60 ))
printf "\n"
printf "Total time taken to execute the sql $line="$TT_HRS:$TT_MINS:$TT_REM_SECS HH:MM:SS
printf "\n"
} > /home/Logs/sql_query_time_$line_$(date +"%Y%m%d_%H%M%S").log
done < $INFILE
rm -f $INFILE $TEMPFILE
exit $RETURN_CODE
You actually need redirection inside the while loop:
while read -r line; do
{ cmd1; cmd2; cmd3; } > "/home/Logs/Sample_${line}_$(date +"%Y%m%d_%H%M%S").log"
done < "$file"
When you have > outfile after done then output is redirected to one file only.

Remove one directory component from path (string manipulation)

I'm looking for the easiest and most readable way to remove a field from a path. So for example, I have /this/is/my/complicated/path/here, and I would like to remove the 5th field ("/complicated") from the string, using bash commands, so that it becomes /this/is/my/path.
I could do this with
echo "/this/is/my/complicated/path/here" | cut -d/ -f-4
echo "/"
echo "/this/is/my/complicated/path/here" | cut -d/ -f6-
but I would like this done in just one easy command, something that would like
echo "/this/is/my/complicated/path" | tee >(cut -d/ -f-4) >(cut -d/ -f6-)
except that this doesn't work.
With cut, you can specify a comma separated list of fields to print:
$ echo "/this/is/my/complicated/path/here" | cut -d/ -f-4,6-
/this/is/my/path/here
So, it's not really necessary to use two commands.
How about using sed?
$ echo "/this/is/my/complicated/path/here" | sed -e "s%complicated/%%"
/this/is/my/path/here
This removes the 5th path element
echo "/this/is/my/complicated/path/here" |
perl -F/ -lane 'splice #F,4,1; print join("/", #F)'
just bash
IFS=/ read -a dirs <<< "/this/is/my/complicated/path/here"
newpath=$(IFS=/; echo "${dirs[*]:0:4} ${dirs[*]:5}")
Anything wrong with a bash script?
#!/bin/bash
if [ -z "$1" ]; then
us=$(echo $0 | sed "s/^\.\///") # Get rid of a starting ./
echo " "Usage: $us StringToParse [delimiterChar] [start] [end]
echo StringToParse: string to remove something from. Required
echo delimiterChar: Character to mark the columns "(default '/')"
echo " "start: starting column to cut "(default 5)"
echo " "end: last column to cut "(default 5)"
exit
fi
# Parse the parameters
theString=$1
if [ -z "$2" ]; then
delim=/
start=4
end=6
else
delim=$2
if [ -z "$3" ]; then
start=4
end=6
else
start=`expr $3 - 1`
if [ -z "$4" ]; then
end=6
else
end=`expr $4 + 1`
fi
fi
fi
result=`echo $theString | cut -d$delim -f-$start`
result=$result$delim
final=`echo $theString | cut -d$delim -f$end-`
result=$result$final
echo $result

bash script and greping with command line

new to bash scripting so just wondering if i am doing this code right at all. im trying to search /etc/passwd and then grep and print users.
usage ()
{
echo "usage: ./file.sk user"
}
# test if we have two arguments on the command line
if [ $# != 1 ]
then
usage
exit
fi
if [[ $# < 0 ]];then
usage
exit
fi
# Search for user
fullname=`grep $1 /etc/passwd | cut -f 5 -d :`
firstname=`grep $1 /etc/passwd | cut -f 5 -d : | cut -f 1 -d " "`
#check if there. if name is founf: print msg and line entry
not sure as how to this or if im doing this right...
am i doing this right?
grep $1 /etc/passwd | while IFS=: read -r username passwd uid gid info home shell
do
echo $username: $info
done
This might work for you:
fullname=$(awk -F: '/'$1'/{print $5}' /etc/passwd)
firstname=${fullname/ *}
You're on the right track.
But I think the 2nd if [[ $# < 0 ]] .... fi block doesn't get you much. Your first test case gets the situation right, 'This script requires 1 argument or quits'.
Also, I don't see what you need firstname for, so a basic test is
case "${fullname:--1}" in
-[1] ) printf "No userID found for input=$1\n" ; exit 1 ;;
* )
# assume it is OK
# do what every you want after this case block
;;
esac
You can of course, duplicate this using "${firstname}" if you really need the check.
OR as an equivalent if ... fi is
if [[ "${fullname}" == "" ]] ; then
printf "No userID found for input=$1\n" ; exit 1
fi
note to be more efficient, you can parse ${fullname} to get firstname without all the calls to grep etc, i.e.
firstname=${fullname%% *}
Let me know if you need for me to explain :--1} and %% *} variable modifiers.
I hope this helps.
Instead of this:
fullname=`grep $1 /etc/passwd | cut -f 5 -d :`
firstname=`grep $1 /etc/passwd | cut -f 5 -d : | cut -f 1 -d " "`
Try this:
fullname=$(cut -f5 -d: /etc/passwd | grep "$1")
if [[ $? -ne 0 ]]; then
# not found, do something
fi
firstname=${fullname%% *} # remove the space and everything after
Note that I changed my answer to cut before grep so that it doesn't get false positives if some other field matches the full name you are searching for.
You can simply by reading your input to an array and then printing out your desired fields, something like this -
grep $1 /etc/passwd | while IFS=: read -a arry; do
echo ${arry[0]}:${arry[4]};
done
Test:
jaypal:~/Temp] echo "root:*:0:0:System Administrator:/var/root:/bin/sh" |
while IFS=: read -a arry; do
echo ${arry[0]}:${arry[4]};
done
root:System Administrator

Error Handling on bash script

Infinite loop on bash script and I want to run forever but (I guess) something goes wrong script is killed. Is there any way like try-catch, just continue to running forever, unconditionaly.
#!/bin/bash
iteration=0
for (( ; ; ))
do
process_id=`ps -ef | grep java | grep TEST | awk '{print $2}' `
kill_command='kill -3 '$process_id
time=`date | awk '{print substr($4,0,5)}' `
last_write=`ls -l /files/*.txt | awk '{print $8}' `
if [ "$time" != "$last_write" ]
then
$kill_command
sleep 1
$kill_command
sleep 1
$kill_command
sleep 1
/test/show_queue.sh
fi
let "iteration+=1"
if [ "$iteration" == "30" ]
then
let "iteration=0"
$kill_command
echo '------------' >> memory_status.log
date >> memory_status.log
prstat -n 7 1 1 >> memory_status.log
echo '------------' >> memory_status.log
/test/show_queue.sh
fi
sleep 60
done
A very simple way to do it is to use two scripts. One with the loop and one which does the killing task :
for (( ; ; ))
do
DoKillingTask
rc=$? # <- You get the return code of the script and decide what to do
done
If it continues to be killed, Mikel (in comment of your question) is right.

Resources