If condition is not working in bash - shell

in the below code its directly going to else condition than if
#!/bin/bash
var=0
if [ "$var" -eq "0" ]
then
echo $var
else
echo $var
fi

This code demonstrably works exactly as given, with no changes whatsoever.
Putting it in a script called testscript, and running PS4=':$LINENO+' bash -x testscript (to print each command invoked preceded by the line number in the source file that it came from), one gets the following output:
:2+var=0
:3+'[' 0 -eq 0 ']'
:5+echo 0
0
Now, let's look at the line numbers in that trace, against the line numbers in the original source file:
#!/bin/bash # line 1
var=0 # line 2 -- :2+var=3
if [ "$var" -eq "0" ] # line 3 -- :3+'[' 0 -eq 0 ']'
then # line 4
echo $var # line 5 -- :5+echo 0
else # line 6
echo $var # line 7
fi
...and the fact that we went to line 2, line 3, and line 5 in that order means that we actually took the truth branch, not the else branch.

#!/bin/bash
var=0
if [ "$var" -eq "0" ]
then
echo $var
echo "Hello"
else
echo $var
echo "ELSE"
fi
It will print: 0 Hello
means it is going if loop only. Check once

Related

How to use if then in bash

We have a text file with 4 lines and a different number of words and i want to count the words per line and see if the number is even or odd but the result keeps saying
./oddwords.sh: line 14: syntax error near unexpected token `then'
./oddwords.sh: line 14: ` if[ n % 2 == 0 ]; then'
This is what ive done so far
#!/bin/bash
if [ $# -ne 1 ] ; then
{
echo "Wrong number of arguments!"
exit 1
}
fi
while read line ; do
n= echo "$(echo $line | wc -w)"
if[ n % 2 == 0 ]; then
echo "Is even"
else
echo "Is odd"
fi
done < $1
Using double brackets like
if [[ n % 2 == 0 ]]; then
and making the numbers variables at the top instead of just saying 2
a=2
b=1
at the beginning then referencing them later in the if statement like
if [[ $a%2 == 0 ]]; then
if you still have issues try
if [[ $a%2 -eq 0 ]]; then
Here are a couple sites i find useful for checking bash when I get stuck on something as well.
https://www.shellcheck.net/
https://explainshell.com/

Bash: multiple conditions in if to check a parameter

So, to run my script I use a parameter like this:
./script 789
The parameter is to change the permissions of a file or a directory, and I'd like to check if every digit is between 0 and 7, so what I tried is the next thing:
if [[ ${1:0:1} -ge 0 && ${1:0:1} -le 7 ]] && [[ ${1:1:1} -ge 0 && ${1:1:1} -le 7]] && [[ ${1:2:1} -ge 0 && ${1:2:1} -le 7]]
then {go ahead with the code}
else
echo "Error: each digit in the parameter must be between 0 and 7"
fi
If it's true, then go ahead with the script, else show an error message, but it doesn't seem to work.
You want to match the parameter with the regex [0-7][0-7][0-7], or [0-7]{3}. In bash, you can do:
[[ "$1" =~ [0-7]{3} ]] || { echo 'invalid parameter' >&2; exit 1; }
Or:
echo "$1" | grep -Eq '[0-7]{3}' || { echo err message >&2; exit 1; }
this could be another way
#!/bin/bash
#Stored input parameter 1 in a variable
perm="$1"
#Checking if inserted parameter is empty, in that case the script will show a help
if [ -z "${perm}" ];then
echo "Usage: $0 <permission>"
echo "I.e.: $0 777"
exit
else
#if the parameter is not empy, check if each digit is between 0-7
check=`echo $perm| grep [0-7][0-7][0-7]`
#if the result of command is empty that means that the input parameter contains at least one digit that's differs from 0-7
if [ -z "${check}" ];then
echo "Error: each digit in the parameter must be between 0 and 7"
fi
fi
that's the output
[shell] ➤ ./test9.ksh 999
Error: each digit in the parameter must be between 0 and 7
[shell] ➤ ./test9.ksh 789
Error: each digit in the parameter must be between 0 and 7
[shell] ➤ ./test9.ksh 779
Error: each digit in the parameter must be between 0 and 7
[shell] ➤ ./test9.ksh 777

Bash script syntax error

anothervar = 1
while [$anothervar -lt 1 ] do
read a
if [ 42 = $a ]; then
$anothervar = 2
else
echo $a
fi
done
get line 9: syntax error near unexpected token `done' error.
What did i do wrong ?
If you paste your shell script into ShellCheck you will see the following two shell script analysis messages for line 2 of your shell script:
You need a space after the [ and before the ].
Use semicolon or linefeed before 'do' (or quote to make it literal).
Your shell script after making the two corrections to line 2 suggested by the automated shell script analysis and changing the first line to anothervar=0 so that the commands inside the while loop can be executed is:
anothervar=0
while [ $anothervar -lt 1 ]; do # fixes 2 errors in this line
read a
if [ 42 = $a ]; then
anothervar=2
else
echo $a
fi
done
Alternatively:
anothervar=0
while [[ $anothervar -lt 1 ]]
do
read a
if [[ 42 = $a ]]
then
anothervar=2
else
echo $a
fi
done
Voila, no semicolons, and spaces in the variables don't bother you anymore. ;-)

Reading user-input in the same line as the script execution

I have a BASH script named fib.sh. The script reads a user input (number) and performs a calculation. I want to be able to type
$ ./fib.sh 8
where 8 is the input
Currently, I have to wait for the next line to enter the input.
$ ./fib.sh
$ 8
Script
#!/bin/bash
read n
a=0
b=1
count=1
fib=$a
while [ $count -lt $n ];
do
fib=$[$a+$b]
a=$b
b=$fib
count=$[$count+1]
done
echo "fib $n = $fib"
exit 0
So you want to pass a parameter to the script instead of reading it. In this case, use $1 as shown here:
#!/bin/bash
n=$1 <---- this will take from the call of the script
echo "I have been given the parameter $n"
a=0
b=1
count=1
fib=$a
while [ $count -lt $n ];
do
fib=$[$a+$b]
a=$b
b=$fib
count=$[$count+1]
done
echo "fib $n = $fib"
exit 0

is there a way to check if a bash script is complete or not?

I'm trying to implement a REPL (read-eval-print loop) in bash. If such a thing already exists, please ignore the following and answer this question with a pointer to it.
Let's use this script as an example (name it test.sh):
if true
then
echo a
else
echo b
fi
echo c
What I want to do is to read this script line by line, check if what I have read so far is a complete bash expression; if it is complete, eval it; otherwise keep on reading the next line. The script below illustrates my idea hopefully (it does not quite work, though).
x=""
while read -r line
do
x=$x$'\n'$line # concatenate by \n
# the line below is certainly a bad way to go
if eval $x 2>/dev/null; then
eval $x # code seems to be working, so eval it
x="" # empty x, and start collecting code again
else
echo 'incomplete expression'
fi
done < test.sh
Motivation
For a bash script, I want to parse it into syntactically complete expressions, evaluate each expression, capture the output, and finally mark up the source code and output (say, using Markdown/HTML/LaTeX/...). For example, for a script
echo a
echo b
What I want to achieve is the output like this:
```bash
echo a
```
```
a
```
```bash
echo b
```
```
b
```
instead of evaluating the whole script and capture all the output:
```bash
echo a
echo b
```
```
a
b
```
bash -n -c "$command_text"
...will determine whether your $command_text is a syntactically valid script without actually executing it.
Note that there's a huge breadth of space between "syntactically valid" and "correct". Consider adopting something like http://shellcheck.net/ if you want to properly parse the language.
The following scripts should generate the Markdown output you expect.
eval "set -n; $x" is used to verify if the command is complete, by checking for syntax errors in the command. Only a command that has no syntax errors will be considered complete, executed, and shown in the output Markdown.
Please note that the input script that is to be processed is executed in a sub-shell and therefore will not interfere with the processing script itself (i.e. the input script can use the same variable names as the processing script and cannot change the values of variables in the processing script). The only exception are the special variables called ___internal__variable___.
There are two approaches to how to achieve that, which I present below. In Version 1, whenever a new complete command is processed, all the statements before it are executed to create a "context" for the command. This effectively runs the input script multiple times.
In Version 2, the environment of the sub-shell is stored in a variable after each complete command is executed. Then, before the next command is executed, the previous environment is restored in the sub-shell.
Version 1
#!/bin/bash
x="" # Current
y="" # Context
while IFS= read -r line # Keep indentation
do
[ -z "$line" ] && continue # Skip empty lines
x=$x$'\n'$line # Build a complete command
# Check current command for syntax errors
if (eval "set -n; $x" 2> /dev/null)
then
# Run the input script up to the current command
# Run context first and ignore the output
___internal_variable___="$x"
out=$(eval "$y" &>/dev/null; eval "$___internal_variable___")
# Generate command markdown
echo "=================="
echo
echo "\`\`\`bash$x"
echo "\`\`\`"
echo
# Generate output markdown
if [ -n "$out" ]
then
echo "Output:"
echo
echo "\`\`\`"
echo "$out"
echo "\`\`\`"
echo
fi
y=$y$'\n'$line # Build context
x="" # Clear command
fi
done < input.sh
Version 2
#!/bin/bash
x="" # Current command
y="true" # Saved environment
while IFS= read -r line # Keep indentation
do
[ -z "$line" ] && continue # Skip empty lines
x=$x$'\n'$line # Build a complete command
# Check current command for syntax errors
if (eval "set -n; $x" 2> /dev/null)
then
# Run the current command in the previously saved environment
# Then store the output of the command as well as the new environment
___internal_variable_1___="$x" # The current command
___internal_variable_2___="$y" # Previously saved environment
out=$(bash -c "${___internal_variable_2___}; printf '<<<BEGIN>>>'; ${___internal_variable_1___}; printf '<<<END>>>'; declare -p" 2>&1)
# Separate the environment description from the command output
y="${out#*<<<END>>>}"
out="${out%%<<<END>>>*}"
out="${out#*<<<BEGIN>>>}"
# Generate command markdown
echo "=================="
echo
echo "\`\`\`bash$x"
echo "\`\`\`"
echo
# Generate output markdown
if [ -n "$out" ]
then
echo "Output:"
echo
echo "\`\`\`"
echo "$out"
echo "\`\`\`"
echo
fi
x="" # Clear command
fi
done < input.sh
Example
For input script input.sh:
x=10
echo "$x"
y=$(($x+1))
echo "$y"
while [ "$y" -gt "0" ]
do
echo $y
y=$(($y-1))
done
The output will be:
==================
```bash
x=10
```
==================
```bash
echo "$x"
```
Output:
```
10
```
==================
```bash
y=$(($x+1))
```
==================
```bash
echo "$y"
```
Output:
```
11
```
==================
```bash
while [ "$y" -gt "0" ]
do
echo $y
y=$(($y-1))
done
```
Output:
```
11
10
9
8
7
6
5
4
3
2
1
```
Assume your test commands are stored in a file called "example". That is, using same commands than in previous answer:
$ cat example
x=3
echo "$x"
y=$(($x+1))
echo "$y"
while [ "$y" -gt "0" ]
do
echo $y
y=$(($y-1))
done
the command:
$ (echo 'PS1=; PROMPT_COMMAND="echo -n =====; echo"'; cat example2 ) | bash -i
produces:
=====
x=3
=====
echo "$x"
3
=====
y=$(($x+1))
=====
echo "$y"
4
=====
=====
=====
while [ "$y" -gt "0" ]
> do
> echo $y
> y=$(($y-1))
> done
4
3
2
1
=====
exit
if you are interested also in the intermediate results of a loop, the command:
$ ( echo 'trap '"'"'echo; echo command: $BASH_COMMAND; echo answer:'"'"' DEBUG'; cat example ) | bash
results in:
command: x=3
answer:
command: echo "$x"
answer:
3
command: y=$(($x+1))
answer:
command: echo "$y"
answer:
4
command: [ "$y" -gt "0" ]
answer:
command: echo $y
answer:
4
command: y=$(($y-1))
answer:
command: [ "$y" -gt "0" ]
answer:
command: echo $y
answer:
3
command: y=$(($y-1))
answer:
command: [ "$y" -gt "0" ]
answer:
command: echo $y
answer:
2
command: y=$(($y-1))
answer:
command: [ "$y" -gt "0" ]
answer:
command: echo $y
answer:
1
command: y=$(($y-1))
answer:
command: [ "$y" -gt "0" ]
answer:
Addendum 1
It is not difficult to change the previous results to some other format. By example, this small perl script:
$ cat formatter.pl
#!/usr/bin/perl
#
$state=4; # 0: answer, 1: first line command, 2: more command, 4: unknown
while(<>) {
# print $state;
if( /^===COMMAND===/ ) {
print "===\n";
$state=1;
next;
}
if( $state == 1 ) {
print;
$state=2;
next;
}
if( $state == 2 && /^>+ (.*)/ ) {
print "$1\n";
next;
}
if( $state == 2 ) {
print "---\n";
$state=0;
redo;
}
if( $state == 0 ) {
print;
next;
}
}
when used in command:
( echo 'PS1="===COMMAND===\n"'; cat example ) | bash -i 2>&1 | ./formatter.pl
gives this result:
===
x=3
===
echo "$x"
---
3
===
y=$(($x+1))
===
echo "$y"
---
4
===
===
===
while [ "$y" -gt "0" ]
do
echo $y
y=$(($y-1))
done
---
4
3
2
1
===
exit
In lieu of pidfiles, as long as your script has a uniquely identifiable name you can do something like this:
#!/bin/bash
COMMAND=$0
# exit if I am already running
RUNNING=`ps --no-headers -C${COMMAND} | wc -l`
if [ ${RUNNING} -gt 1 ]; then
echo "Previous ${COMMAND} is still running."
exit 1
fi
... rest of script ...

Resources