I want to make a build chain script, and I don't want it to perform until the end if there are error during compilation.
It's the first time I write a more "elaborated" script in bash, and it just doesn't work:
it doesn't echo ERROR although I have lines with the word error in it
whatever the value of testError, the script just hangs in the line
this is the code:
testError=false
output=$(scons)
while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then echo 'ERROR' ; $testError = true ; fi #$testError = true fi
done
echo $testError
if $testError ; then exit ; fi;
... other commands
EDIT: Following all posters answers and Bash setting a global variable inside a loop and retaining its value -- Or process substituion for dummies and How do I use regular expressions in bash scripts?,
this is the final version of the code.
It works:
testError=false
shopt -s lastpipe
scons | while read -r line; do
if [[ $line =~ .*[eE]rror.* ]] ; then
echo -e 'ERROR'
testError=true
fi
echo -e '.'
done
if $testError ; then
set -e
fi
You set the value of testError in a subshell induced by your pipeline. When that subshell exits (at the end of the pipeline), any changes you made disappear. Try this:
while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then
echo -e 'ERROR'
testError=true
fi #$testError = true fi
done < <( scons )
or, if you don't want or can't use process substitution, use a temporary file
scons > tmp
while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then
echo -e 'ERROR'
testError=true
fi #$testError = true fi
done < tmp
This eliminates the pipeline, so the changes to testError persist after the while loop.
And, if your version of bash is new enough (4.2 or later), there is an option that allows the while loop at the end of a pipeline to execute in the current shell, not a subshell.
shopt -s lastpipe
scons | while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then
echo -e 'ERROR'
testError=true
fi #$testError = true fi
done
You should try
set -e
this stops the script to continue if a command exit with a non zero status
or better
error_case() { # do something special; }
trap 'echo >&2 "an error occurs"; error_case' ERR
this run error_case function each time a command exit with a non zero status
See http://mywiki.wooledge.org/BashFAQ/105
Another bug is that you have spaces in the assignment. And skip the $
$testError = true
should be
testError=true
EDIT
testerror is changed in the subshell. Try
testerror=$(
scons | while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then
echo true
fi #$testError = true fi
done
)
Are you trying to parse the output of scons?
This:
output=$(scons)
while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then
echo 'ERROR'
testError=true
fi
done
does not do that. Perhaps you want:
scons | while read -r line; do ... ; done
I answer also because other answers didn't notice: the use of regular expression should be done this way, using =~and not ==:
if [[ $line =~ .*[eE]rror.* ]] ; then
...
cf How do I use regular expressions in bash scripts?
Related
This question already has answers here:
Test if a command outputs an empty string
(13 answers)
Closed 4 years ago.
Some Example:
I've a shellscript where I want to check if a the stdout of a command is empty.
So I can do
if [[ $( whateverbin | wc -c) == 0 ]] ; then
echo no content
fi
But is there no direct command, to check this? Something like :
if whateverbin | checkifstdinisempty ; then
echo no content
fi
You could use the -z conditional expression to test if a string is empty:
if [[ -z $(ls) ]]; then echo "ls returned nothing"; fi
When you run it on an empty result, the branch gets executed:
if [[ -z $(cat non-existing-file) ]]; then echo "there was no result"; fi
Just try to read exactly one character; on no input, read will fail.
if ! whateverbin | IFS= read -n 1; then
echo "No output"
fi
If read fails, the entire pipeline fails, and the ! negates the non-zero exit status so that the entire condition succeeds.
[[ `echo` ]] && echo output found || echo no output
--> no output
[[ `echo something` ]] && echo output found || echo no output
--> output found
With if :
if [ `echo` ] ; then echo ouput found; else echo no output; fi
When I try to match a string and do some conditions; it always fails to do so.
date=`date +%Y%m%d`
kol="/home/user/test_$date"
regex='Terminating the script'
if [ -f $kol ]; then
sudo tail -f $kol | while read line; do
if [[ $line = *"Terminating the"* ]]
then
echo "failed"
else
echo $line >> /home/user/test123_$date
fi
else
echo "File is not yet present"
exit 0
fi
I have also tried with regex and that to failed. So when ever I input the matching string into the file ($path) it wont output "failed"; Is there anything wrong in the code. Help is much appreciated.
You can try this. At least it works for me:
sudo tail -f $1 | while read line; do
if [[ $line = '' ]]
then
break
fi
if [[ $line = *"Terminating the"* ]]
then
echo "failed"
else
echo $line >> /home/nurzhan/test123_$date
fi
done < "$1"
$path is parameter to the running script. Also note that by default the command tail returns only 10 last lines.
I don't use Bash very frequently but I need to work on a bit of Bash that has to make a curl request to a web service with a query string tail built from command arguments contained in the script command arguments variable $#. So if the command argument string is something like -e something -v -s somethingelse, then the query string that must be produced is e=something&v=blank&s=somethingelse.
At the moment my test script looks like this:
#!/bin/bash
set -e
echo "${#}"
query_string=""
for arg in "${#}" ; do
if [[ "${arg}" =~ "-" ]] ; then
query_string+="${arg}="
else
if [ -z "${arg}" ] ; then
query_string+="blank&"
else
query_string+="${arg}&"
fi
fi
done
echo "${query_string}" | tr -d '-'
and produces the incorrect output
e=something&v=s=somethingelse&
I'm not sure where I'm going wrong. Any suggestions would be appreciated.
How about:
#!/bin/bash
set -e
echo "${#}"
option=true
query_string=""
for arg in "${#}" ; do
if $option ; then
query_string+="${arg}="
option=false
else
if [[ "${arg}" =~ "-" ]] ; then
query_string+="blank&${arg}="
else
query_string+="${arg}&"
option=true
fi
fi
done
echo "${query_string::-1}" | tr -d '-'
Every iteration you have to check the previous arg to see if it was a switch, not a value:
#!/bin/bash
set -e
echo "${#}"
prev_arg=""
query_string=""
for arg in "${#}" ; do
if [[ $arg == -* ]] ; then
# Current arg is a switch and the previous one was also a switch
# which means the value for it is blank
if [[ $prev_arg == -* ]] ; then
query_string+="blank&"
fi
query_string+="${arg}="
else
query_string+="${arg}&"
fi
prev_arg="$arg"
done
echo "${query_string::-1}" | tr -d '-'
This produces the following output:
something -v -s somethingelse
e=something&v=blank&s=somethingelse
I have a script that must be able to accept both by files and stdin on the first argument. Then if more or less than 1 arguments, reject them
The goal that I'm trying to accomplish is able to accpet using this format
./myscript myfile
AND
./myscript < myfile
What I have so far is
if [ "$#" -eq 1 ]; then #check argument
if [ -t 0 ]; then #check whether input from keyboard (read from github)
VAR=${1:-/dev/stdin} #get value to VAR
#then do stuff here!!
else #if not input from keyboard
VAR=$1
if [ ! -f "$VAR" ]; then #check whether file readable
echo "ERROR!"
else
#do stuff heree!!!
fi
fi
fi
The PROBLEM is when I tried to say
./myscript < myfile
it prints
ERROR!
I dont know whether this is the correct way to do this, I really appreciate for suggestion or the correct code for my problem. Thank you
#!/bin/bash
# if nothing passed in command line pass "/dev/stdin" to myself
# so all below code can be made branch-free
[[ ${#} -gt 0 ]] || set -- /dev/stdin
# loop through the command line arguments, treating them as file names
for f in "$#"; do
echo $f
[[ -r $f ]] && while read line; do echo 'echo:' $line; done < $f
done
Examples:
$ args.sh < input.txt
$ args.sh input.txt
$ cat input.txt | args.sh
Can anyone see whats wrong here? If I put X|9 in lan.db (or any db in this directory) and run the following code, the IF statement does not work. It's weird! if you echo $LINE, it is indeed pulling X|9 out of lan.db (or any db in this directory) and setting it equal to LINE, but it wont do the comparison.
DBREGEX="^[0-9]|[0-9]$"
shopt -s nullglob
DBARRAY=(databases/*)
i=0
for i in "${!DBARRAY[#]}"; do
cat ${DBARRAY[$i]} | grep -v \# | while read LINE; do
echo "$LINE" (Whats weird is that LINE DOES contain X|9)
if [[ !( $LINE =~ $DBREGEX ) ]]; then echo "FAIL"; fi
done
done
If however I just manually sent LINE="X|9" the same code (minus the while) works fine. ie LINE=X|9 fails, but LINE=9|9 succeeds.
DBREGEX="^[0-9]|[0-9]$"
Comment shopt -s nullglob
Comment DBARRAY=(databases/*)
Comment i=0
Comment for i in "${!DBARRAY[#]}"; do
Comment cat ${DBARRAY[$i]} | grep -v \# | while read LINE; do
LINE="X|9"
if [[ !( $LINE =~ $DBREGEX ) ]]; then echo "FAIL"; fi
Comment done
Comment done
* UPDATE *
UGH I GIVE UP
Now not even this is working...
DBREGEX="^[0-9]|[0-9]$"
LINE="X|9"
if [[ ! $LINE =~ $DBREGEX ]]; then echo "FAIL"; fi
* UPDATE *
Ok, so it looks like I have to escape |
DBREGEX="^[0-9]\|[0-9]$"
LINE="9|9"
echo "$LINE"
if [[ ! $LINE =~ $DBREGEX ]]; then echo "FAIL"; fi
This seems to work ok again
| has a special meaning in a regular expression. ^[0-9]|[0-9]$ means "starts with a digit, or ends with a digit". If you want to match a literal vertical bar, backslash it:
DBREGEX='^[0-9]\|[0-9]$'
for LINE in 'X|9' '9|9' ; do
echo "$LINE"
if [[ ! $LINE =~ $DBREGEX ]] ; then echo "FAIL" ; fi
done
You don't need round brackets in regex evaluation. You script is also creating a sub shell and making a useless use of cat which can be avoided.
Try this script instead:
while read LINE; do
echo "$LINE"
[[ "$LINE" =~ $DBREGEX ]] && echo "PASS" || echo "FAIL"
done < <(grep -v '#' databases/lan.db)