Bash: how do I convert a command with a number result then compare it with an operator - bash

I am trying to execute a word count command on a log file and if the file has the "error" string, I want to take some action, but I can't seem to properly convert the grep to word count command to a real number so it compares properly to the greater than zero. So far with several variations, the conditional statement is always true.
if ((grep -Ei "error" myfile.log | wc -l)) > 0; then echo 1; else echo 0; fi

First of all, you can write conditions based on the exit code of programs.
If grep finds a matching line, it exits with success:
if grep -qEi "error" myfile.log; then echo 1; else echo 0; fi
I added the -q flag to not print the matching line, as you probably don't need it.
I strongly recommend to use the above solution, without wc.
But for the sake of completeness, here's some more explanation about different ways of comparing numbers.
One way to compare numbers is with -gt ("greater than") within [ ... ]:
if [ $(grep -Ei "error" myfile.log | wc -l) -gt 0 ]; then echo 1; else echo 0; fi
You can read about other operators within [ ... ] in help test.
Or using arithmetic context within ((...)):
if (($(grep -Ei "error" myfile.log | wc -l) > 0)); then echo 1; else echo 0; fi
Notice that in both of these examples I wrapped the grep ... | wc -l within a $(...) sub-shell to capture the output.
The syntax you wrote is incorrect.

Related

Bash comparison between numbers fail

I think I'm missing something with variable types...why the following script, that is supposed to read a number of lines and compare this number to 1 always enters the if even though it returns exactly 1?
status() {
lines=`ps aux | grep myprocess | wc -l` #returns 1
if [ $lines -gt 1 ]; then
echo "Process is up"
else
echo "Process is down"
fi
}
Solved by using melpomene's suggestion to use egrep instead of grep.
Strangely enough that command had several lines printed when ran as init script.

Getting the line count of a file in a shell script with wc failing

my script check if the arguments are files or folders
if it is a file, he count the number of lines
after that, if the number of lines is great then 20 or less he do some instructions
the problem is in this instructionn= cat $a | wc -l
My script:
#!/usr/bin/env bash
echo 'Hello this is the test of' `date`
echo 'arguments number is ' $#
if [ $# -eq 4 ]
then
for a in $#
do
if [ -d $a ]
then
ls $a > /tmp/contenu
echo "contenu modified"
elif [ -f $a ]
then
# this instruction must set a numeric value into n
echo "my bad instruction"
n= cat $a | wc -l
echo "number of lines = " $n
# using the numeric value in a test (n must be numeric and takes the number of lines in the current file)
if [ $n -eq 0 ]
then
echo "empty file"
elif [ $n -gt 20 ]
then
echo ` head -n 10 $a `
else
cat $a
fi
else
echo "no file or directory found"
fi
done
else
echo "args number must be 4"
fi
This is the output of the execution of the incorrect instruction
my bad instruction
5
number of lines =
ExamenEx2.sh: line 19: [: -eq : opérateur unaire attendu
The line n= cat $a | wc -l is an offending instruction. Always remember that bash shell scripting is extremely case-sensitive. Your command is interpreted by the shell as having to run two separate commands
n= cat $a | wc -l
#^^ ^^^^^^^^^^^^^^
#1 2
The first part just stores an empty string to the variable n and the next prints the line count of the file stored in variable a. Notice that the shell does not throw errors for this. Because it is not violating the syntax (just the semantics are wrong). But the line count is never assigned to the variable n.
The error is seen when the conditional if [ $n -eq 0 ] is hit when you are doing a comparison with an empty variable on the LHS.
You wanted to run a command and store its output, you need command-substitution($(..)) for that. Assuming the $a contains a name of a file just do
n=$(wc -l < "$a")
Note, that I've removed the useless cat usage and piping it to wc. But wc can read from an input stream directly.
Also note that you have multiple bad practices in your script. Remember to do the following
Always double-quote the shell variables - "$#", "$#", [ -f "$a" ], [ -d "$a" ]
Don't use the `` for command-substitution, because it is not easily nestable and you might have issues related to quoting also.
You can use conditional expression [[ if you are sure if the script is running under bash in which a variable containing spaces can be used without quoting on the LHS

Return an error if input doesn't have exactly 1 line, otherwise pipe input to next step

I have a series of commands chained together with pipes:
should_create_one_line | expects_one_line
The first command should_create_one_line should produce an output that only has one line, but under strange circumstances it is possible for the output to be multiline or empty.
I would like to add a step in between these two, validate_one_line:
should_create_one_line | validate_one_line | expects_one_line
If its input contains exactly 1 line then validate_one_line will simply output its input. If its input contains more than 1 line or is empty then validate_one_line should cause the whole sequence of steps to stop and return an error code.
What command can I use for validate_one_line?
Use read. Here's a shell function that meets your specs:
exactly_one_line() {
local line # Use to echo the line
read -r line || return # Guarantee at least one line is read
read && return 1 # Indicate failure if another line is successfully read
echo "$line"
}
Notes
"One line" assumes a single line followed by a newline. If your input could be like, a file with contents but no newlines, then this will fail.
Given a pipeline like a|b, a cannot prevent b from running. At a minimum, b needs to handle when a produces no output.
Demo:
$ wc -l empty oneline twolines
0 empty
1 oneline
2 twolines
3 total
$ exactly_one_line < empty; echo $?
1
$ exactly_one_line < oneline; echo $?
oneline
0
$ exactly_one_line < twolines; echo $?
1
First off, you should seriously consider adding the validation code to expects_one_line. According to this post, each process starts in its own subshell, meaning that even if validate_one_line fails, you will get an error in expects_one_line because it will try to run with no input (or a blank line). That being said, here is a bash one-liner that you can insert into your pipe to validate:
should_create_one_line.sh | ( var="$(cat)"; [ $(echo "$var" | wc -l) -ne 1 ] && exit 1 || echo "$var") | expects_one_line.sh
The problem here is that when the validation subshell returns in the exit 1 case, expects_one_line.sh will still get a single blank line. If this works for you, then great. If not, it would be better to just put the following into the beginning of expects_one_line.sh:
input="$(cat)"
[ $(echo "$var" | wc -l) -ne 1 ] && exit 1
This would guarantee that expects_one_line.sh fails properly when getting a single line without having to wonder about what the empty line that the validation outputs will do to the script.
You may find this post helpful: How to read mutliline input from stdin into variable and how to print one out in shell(sh,bash)?
You can use a bash script to check the incoming data and call the other command when the input is only 1 line
The following code starts cat when it is ONLY fet in 1 line
sh -c 'while read CMD; do [ ! -z "$LINE" ] && exit 1; LINE=$CMD; done; [ -z "$LINE" ] && exit 1; printf "%s\n" $LINE | "$0" "$#"' cat
How this works
Try reading a line, if failed go to step 5
If variable $LINE is NOT empty, goto step 6
Save line inside variable $LINE
Goto step 1
If $LINE is NOT empty, goto step 7
Exit the program with status code 1
Call our program and pass our $line to it using printf
Example usage:
Printing out only if grep found 1 match:
grep .... | sh -c 'while read CMD; do [ ! -z "$LINE" ] && exit 1; LINE=$CMD; done; [ -z "$LINE" ] && exit 1; printf "%s\n" $LINE | "$0" "$#"' cat
Example of the question poster:
should_create_one_line | sh -c 'while read CMD; do [ ! -z "$LINE" ] && exit 1; LINE=$CMD; done; [ -z "$LINE" ] && exit 1; printf "%s\n" $LINE | "$0" "$#"' expects_one_line

Why does my script report ls: not found

I have the following korn script:
#!/bin/ksh
TAPPDATADIR=/hp/qa02/App/IPHSLDI/Data
echo $TAPPDATADIR
if [[ls $TAPPDATADIR/zip_file_MD5_checksum*.txt | wc -l > 1]]
then
exit "asdf"
fi
When I attempt to run it I get:
/hp/qa02/App/IPHSLDI/Data
./iftest.ksh: line 7: [[ls: not found
Why isn't my if statement working?
I'm trying to see if there are multiple checksum files in the Data directory. If there are I want to exit the script.
There are several problems:
There shouldn't be any spaces around = in the assignment.
You need spaces around [[ and ]] in the if statement.
To substitute the result of a command into the test expression, you need to use backticks or $(...).
The parameter to exit should be a number, I think you just want to echo the string.
> performs string comparison, you have to use -gt to perform numeric comparison.
So the full script should be:
#!/bin/ksh
TAPPDATADIR=/hp/qa02/App/IPHSLDI/Data
echo $TAPPDATADIR
if [[ $(ls $TAPPDATADIR/zip_file_MD5_checksum*.txt | wc -l) -gt 1 ]]
then
echo "asdf"
fi

shell script : how to validate function is returning 1 text line?

I try to generate a csv file through a lot of functions like that :
function get_sudo_version {
sudo -V 2>/dev/null|grep -i "sudo version"
}
sudo_version=$(get_sudo_version)
Function above is a simple example but in some cases i cannot be sure the output is correct.
I would like to know what is the best way to validate the function return one text line only.
I thought about something like that
function validate_output {
output=$1;
echo $1|grep -q "\n";
echo $?;
}
mytest="val1
err2
err3"
But it's obviously not working because the variable does not keep the retrun line character:
echo $mytest
val1 err2 err3
So if someone has a good idea of how i could wirte a generic check function i would be glad.
Thanks
If you have GNU grep, you could simply ensure that grep doesn't produce more than one line of output in the first place via grep -m 1. Alternatively, use sed '/sudo version/!d;q' instead of grep.
A function that simply checks lines of input while passing them through might look like:
shopt -s lastpipe # if bash
# ksh or bash
validate_output() {
(( $(tee >(wc -l) >&2) == 1 ))
} 2>&1
# bash
validate_output2() {
local lines
tee /dev/fd/2 | mapfile -tc1 -C '((++lines < 2)) || return; :'
((lines))
} 2>&1
get_sudo_version | validate_output || echo failed
Many variations on that possible of course. IMO it's pretty pointless and shouldn't be used for something like this. Just design your get_sudo_version so that it guarantees the right results.
You can count lines with wc -l:
$ LINES="$(echo -n | wc -l)"; [[ "$LINES" -gt 0 ]] && echo 'at least one line'
$ LINES="$(echo -e 'a\nb\nc' | wc -l)"; [[ "$LINES" -gt 0 ]] && echo 'at least one line'
at least one line

Resources