bash: can read be used as a command or expression? - bash

I want to make a oneliner loop that reads and checks what it read.
This surely won't work:
while [[ read line != "q" ]]; do; echo "enter q to quit: "; done
Zsh here tells me condition expected: read.
Perhaps read does not even have this concept of a return value.

Bash Pitfall #9:
if takes a command. [ is a command, not a syntax marker for the if statement. It's equivalent to the test command, except that the final argument must be a ].
[[ is not special syntax for the if condition. [[ is a command in its own right. Try a loop like this:
while read -r line; do
[[ $line = "q" ]] && break
# more code
done
read returns failure when it cannot read an expression, and [[ is a command, so this works. For more info see http://mywiki.wooledge.org/BashFAQ/001.
Note: Suuplying the -r switch to read is a good idea - it prevents funny things happening to backslashes in the input.

while read line; do; [[ "$line" == 'q' ]] && break; done;
seems to work.
But I'm not sure why I can't replace [[ ]] with [ ], it fails strangely:
$ bash
bash-3.2$ while true; do; read line; [ "$line" == 'q' ] && break; done;
bash: syntax error near unexpected token ';'
$ while true; do; read line; [ "$line" == 'q' ] && break; done;
ajlfkj
zsh: = not found

Related

Shell - Skip matching lines while read

I'm looking for help with my shell... Hope I'll find it here...
Here's my code :
#!/bin/sh
while IFS= read -r line || [ -n "$line" ]
do
[[ "$line" =~ ^[[:space:]]*\# ]] && continue # This line must stay
[[ "$line" =~ *read[[:space:]]-r[[:space:]]line* ]] && continue
echo "${line%$NL}"
done < $0
First test will suppress "only comment lines".
Second test purpose is to suppress the "while IFS= read..." lines - no matter what, it's just a test :-)
"done < $0" has been here written intentionaly... for the test !
Running the shell outputs this :
while IFS= read -r line || [ -n "$line" ]
do
[[ "$line" =~ ^[[:space:]]*\# ]] && continue # This line must stay
[[ "$line" =~ *read[[:space:]]-r[[:space:]]line* ]] && continue
echo "${line%$NL}"
done < $0
as I thought the first line will be gone because of matching then 2nd test.
What's my mistake ?
For the record, I don't want to use extra sed or awk sentence.
Actually, the input data (here $0 file) has to be standard input (eg extract from tee command). I read lot of stackOverflow subject about this, with sed or awk responses that didn't match my purpose.
The regex is invalid. A short test shows:
> [[ 'while IFS= read -r line || [ -n "$line" ]' =~ *read[[:space:]]-r[[:space:]]line* ]]
> echo $?
2
The * can't be "alone" - it can't be the first character in a POSIX extended regular expression. It has to "bind" to something, ex. a dot .. A dot represents any character. You want:
[[ $line =~ .*read[[:space:]]-r[[:space:]]line.* ]]
Problem is presence of starting quantifier * in the regex in second continue line. You may use:
[[ "$line" =~ read[[:space:]]+-r[[:space:]]+line ]] && continue
There is no need to match anything before read or after line in this regex.
Also it is better to use quantifier + after [[:space:]] to make it match 1 or more white spaces.
You can do more refactoring and combine both regex into one by using alternation as in this code:
while IFS= read -r line || [[ -n $line ]]
do
[[ $line =~ ^[[:space:]]*#|read[[:space:]]+-r[[:space:]]+line ]] && continue
echo "${line%$NL}"
done < $0

[[Bash]] Search for combined Expressions in every row

I am very new to Bash Scripting and I have a question regarding my CheckOurCodingRules.sh script:
I want to search for every 'hPar,' in a textfile and if found it should be checked if there is a also a 'const' in the same row.
Thats what I got so far but there is something wrong here:
while read line
do
if [[ $line == *hPar\,* ]] && [[ $line == *const\*]];then
DOCUMENTATION_TEST_A=1
else
echo DOCUMENTATION_TEST_A=0
fi
done < $INPUT_FILE
if [[DOCUMENTATION_TEST_A=0]];then
echo "error: Rule1: No const before hpar"
fi
There are a couple of issues with your script, see the code below which works for me:
DOCUMENTATION_TEST_A=0 # initial value
while read line
do
# spaces between conditional and brackets, no backslashes
if [[ $line == *hPar,* ]] && [[ $line == *const* ]]
then
DOCUMENTATION_TEST_A=1
break # optional, no need to scan the rest of the file
fi
done < $INPUT_FILE
# spaces and $, -eq is used for numerical comparisons
if [[ $DOCUMENTATION_TEST_A -eq 0 ]];
then
echo "error: Rule1: No const before hpar"
fi
A cleaner solution would be to use grep:
if ! grep "hPar," $INPUT_FILE | grep "const" >/dev/null
then
echo "error: Rule1: No const before hpar"
fi

Bash - syntax error near unexpected token `fi'

#!/usr/bin/env bash
if [[ $# -eq '0' ]]
then
var=command
if [[ ${var} -eq '0' ]]
then
do something
else
do something else
fi
fi
if [[ $# -eq '1' ]]
usage;
fi
if [[ $# -eq '2' ]]
if [[ "$1" != "-r" ]]
then
usage;
fi
if [[ "$2" =~ some_pattern ]]
then
do something
else
echo "Pattern is in an improper format. Please enter it as: correct_pattern, and try again"
exit 1
fi
usage="Usage: meta_script.sh -r correct_pattern
-r for reset is used to manually pass a parameter instead of using the default"
exit 1
fi
When I run this script, this is the error I get:
./meta_script.sh: line 31: syntax error near unexpected token `fi'
./meta_script.sh: line 31: `fi'
In the first if statement where I'm checking if the number of parameters are equal to 1, I had put a then, but I got the same error as above, except with then instead of fi. It's almost as if no matter what I put and where, I get these errors and when I remove them to try and fix it, I get another bunch of similar errors. Please help me correct this script. Thanks!
With regard to the segment:
if [[ $# -eq '2' ]]
if [[ "$1" != "-r" ]]
then
you are missing the then for the first if statement. Putting that in should get you past that error:
if [[ $# -eq 2 ]] ; then
if [[ "$1" != "-r" ]] ; then
You'll see I've put the then on the same line since it's a good habit to get into, realising that if and then always go together (same as while and do). It also allows you to see more lines of "useful" code on any given terminal :-)
I've also gotten rid of the useless quotes around 2 since $# always returns a numeric value. I'd advise sticking to using quotes just for strings.

Bash print the number of incorrect file line

A beginner asking for help (:
So, I have a script that checks brackets in a text file and tells whether they are closed correctly. However, I also want to make my script print out the number of the incorrect line (where brackets are closed incorrectly). I have tried counting file lines and then making a nested while loop, however, it doesn't work for me at all ): Are there any simple solutions for this? I would like to leave the LINE counter if that's possible o:
INPUT="$1"
count=0
LINE=0
# Check if file exists
[ ! -f $INPUT ] && { echo "file $INPUT do not exist."; exit ; }
# Count file lines and read every char
while IFS= read -r LINE
do
LINE=$(( LINE + 1 ))
while read -n1 char
do
[ "$char" == "(" ] && (( count++ ))
[ "$char" == ")" ] && (( count-- ))
if [ "$count" -lt 0 ]
then
break
fi
done
done < "$INPUT"
if [ "$count" -lt 0 ]
then
echo "Found a mistake in $LINE line "
else
echo "Everything's correct"
fi
You have a couple of problems:
Your read in the inner loop consumes the input from the file, not from LINE.
The line
LINE=$(( LINE + 1 ))
is really wrong: LINE is the content of the line of your file, and your trying to add 1 to it. Weird.
Your break only breaks the inner loop (it should break two loops). Use break 2 for this.
Here's a working version of your script:
input=$1
count=0
linenb=0
# Check if file exists
[[ -f $input ]] || { echo "Error: file $input do not exist."; exit 1; }
# Count file lines and read every char
while IFS= read -r line; do
((++linenb))
while read -n1 char; do
[[ $char == '(' ]] && ((++count))
[[ $char == ')' ]] && ((--count))
((count>=0)) || break 2
done <<< "$line"
done < "$input"
if ((count<0)); then
echo "Found a mistake in line #$linenb:"
printf '%s\n' "$line"
else
echo "Everything's correct"
fi
Note that I used more ((...)) and [[...]].
I also used lowercase variable names, as your computer isn't deaf: you don't need to shout the name of the variable. (And it's nicer to the eye). And it's good practice to use lowercase variable names, as there's no chance that they clash with Bash's own variables.

Error in this bash script

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?

Resources