How to make Bash interpret variable as string for comparison? [duplicate] - bash

This question already has answers here:
How to use double or single brackets, parentheses, curly braces
(9 answers)
Closed 3 years ago.
I have a bash script that stores the output of a file comparison. The variable becomes something like: thing="/path/to/file - differ: byte 2, line 3".
In later lines I want to check that thing is not empty. However, when I try comparing them, it interprets thing as a command and not simply as a string.
My code is somewhat as follows:
#!/bin/bash
thing="/path/to/file - differ: byte 2, line 3"
if ["$thing" != ""]; then
echo
echo "Something went wrong"
else
echo "Everything worked"
fi
Rather than saying thing is not an empty string, I get an error message that says something like
bash: [/path/to/thing - differs: byte2, line 3: No such file or directory.
How can I ensure that a comparison is happening between strings and that thing is not being interpreted as a command?

Try this instead:
#!/bin/bash
thing="/path/to/file - differ: byte 2, line 3"
if [[ -n "$thing" ]]; then
echo "Something went wrong"
else
echo "Everything worked"
fi
Notice the double [[]] pairs. That will make use of Bash's internal test.
Use help test on Bash's command prompt to get more information.
EDIT: As explained by Jonathan in the comments, using any of [ … ] or [[ … ]] or test uses a built-in internal command in Bash. Section CONDITIONAL EXPRESSIONS of man bash (or Conditional Expressions, Conditional Constructs and Bourne Shell Built-ins) explains that. However, while [ … ] and test are logically the same (the only difference is that [ expects its last argument to be ] but test has no analogous expectation), the tests implemented by [[ … ]] are subject to different parsing rules from [ … ] and test, and [[ … ]] implements some extra test capabilities missing from the other.

Related

Verifying bash script inputs

I've just started using Linux as part of my computer science degree.
I'm writing some very simple Bash scripts and I've become a tad bit stuck.
I would like the script I'm attempting to write to be able to differentiate between "non valid inputs ie letters" from "valid inputs ie numbers from a specific range"
Currently the script "works" although I'm having troubles with another echo that I would like only to "echo" when the below line is "not true", is there a simple way to write this? I'm not specifically looking for efficient code, just code that I can learn from and understand at my amateur level.
So, long story short, is it possible to obtain information from the command line below, so that I can have a simple "not true" variable that I can use in another "else" or "elif" command?
For reference line 1 is to detect alphabetical inputs, and line 2 being the line of code I would like to write as "not true" for use in another part of my script.
let xx=$a+1-1 2>/dev/null; ret=$?
if [ $a -ge 7 ] && [ $a -le 70 ] && [ $xx -eq $xx ] && [ $ret -eq 0 ]
I'm not sure I'm explaining it very well, so any help would be appreciated. :)
Welcome to Stack Overflow. :)
Start by reading the docs. I don't mean that in any way to be mean - it's just the best way to go about this.
c.f. this manual
Then read through the BashFAQs
Also, this site is really your friend. Start by familiarizing yourself with how to ask a question well.
For your question, if I read it right:
typeset -i xx # accepts only digits now.
If the input is foo, the value defaults to 0, so now just check the range.
if (( xx >= 7 && xx <= 70 )); then : value is ok
else echo "Value must be a number from 7 to 70"
exit 1
fi
Good luck. :)
One problem with the "variable with integer attribute" is that it still doesn't protect you from invalid input:
$ declare -i aNumber
$ aNumber=1234X
bash: 1234X: value too great for base (error token is "1234X")
See 6.5 Shell Arithmetic for how bash interprets values to be numbers (scroll down to the paragraph starting with "Integer constants follow the C language definition")
In my experience, the best way to check for valid numeric input is with string-oriented pattern matching.
if [[ $1 =~ ^[+-]?[0-9]+$ ]]; then
echo "input $1 is an integer"
fi
In addition to extended regular expressions, bash's advanced pattern matching can also be used within [[...]]
if [[ $1 == ?([+-])+([0-9]) ]]; then
echo "input $1 is an integer"
fi
((...)) is preferred over let. See the let builtin
command for details.
Also the shellcheck wiki entry.

why in an 'if' statement 'then' has to be in the next line in bash?

if is followed by then in bash but I don't understand why then cannot be used in the same line like if [...] then it has to be used in the next line. Does that remove some ambiguity from the code? or bash is designed like that? what is the underlying reason for it?
I tried to write if and then in the same line but it gave the error below:
./test: line 6: syntax error near unexpected token \`fi'
./test: line 6: \`fi'
the code is:
#!/bin/bash
if [ $1 -gt 0 ] then
echo "$1 is positive"
fi
It has to be preceded by a separator of some description, not necessarily on the next line(a). In other words, to achieve what you want, you can simply use:
if [[ $1 -gt 0 ]] ; then
echo "$1 is positive"
fi
As an aside, for one-liners like that, I tend to prefer:
[[ $1 -gt 0 ]] && echo "$1 is positive"
But that's simply because I prefer to see as much code on screen as possible. It's really just a style thing which you can freely ignore.
(a) The reason for this can be found in the Bash manpage (my emphasis):
RESERVED WORDS: Reserved words are words that have a special meaning to the shell. The following words are recognized as reserved when unquoted and either the first word of a simple command (see SHELL GRAMMAR below) or the third word of a case or for command:
! case coproc do done elif else esac fi for function if in select then until while { } time [[ ]]
Note that, though that section states it's the "first word of a simple command", the manpage seems to contradict itself in the referenced SHELL GRAMMAR section:
A simple command is a sequence of optional variable assignments followed by blank-separated words and redirections, and terminated by a control operator. The first word specifies the command to be executed, and is passed as argument zero.
So, whether you consider it part of the next command or a separator of some sort is arguable. What is not arguable is that it needs a separator of some sort (newline or semicolon, for example) before the then keyword.
The manpage doesn't go into why it was designed that way but it's probably to make the parsing of commands a little simpler.
Here's another way to explain the need for a line break or semicolon before then: the thing that goes between if and then is a command (or sequence of commands); if the then just came directly after the command without a delimiter, it'd be ambiguous whether it should be treated as a shell keyword or just an argument to the command.
For instance, this is a perfectly valid command:
echo This prints a phrase ending with then
...which prints "This prints a phrase ending with then". Now, consider this one:
if echo This prints a phrase ending with then
should that print "This prints a phrase ending with then" and look for a then keyword later on, or should it just print "This prints a phrase ending with" and treat the then as a keyword?
In order to settle this ambiguity, shell syntax says it should treat "then" as an argument to echo, and in order to get it treated as a keyword you need a command delimiter (line break or semicolon) to mark the end of the command.
Now, you might think that your if condition [ $1 -gt 0 ], already has a perfectly good delimiter, namely the ]. But in shell syntax, that's really just an argument to the [ command (yes, that's a command). Try this command:
[ 1 -gt 0 ] then
...and you'll probably get an error like "-bash: [: missing ']'", because the [ command checked its last argument to make sure it was "]", found that it was "then" instead, and panicked.
Perhaps it helps to understand why this is so by way of a few examples. The argument to if is a sequence of commands; so you can say e.g.
if read -r -p "What is your name?" name
[ "$name" -eq "tripleee" ]
then
echo "I kneel before thee"
fi
or even a complex compound like
while read -r -p "Favorite number?" number
case $number in
42) true; break;;
*) false;;
esac
do
echo "Review your preferences, then try again"
done
This extremely powerful but potentially confusing feature of the shell is probably one of its most misunderstood constructs. The ability to pass a sequence of commands to the flow control statements can make for very elegant scripts, but is often missed entirely (see e.g. Why is testing "$?" to see if a command succeeded or not, an anti-pattern?)
If it helps, you can use semi-colons
if [ $1 -gt 0 ]; then
echo "$1 is positive"
fi
# or even
if [ $1 -gt 0 ]; then echo "$1 is positive"; fi
As for why, it helps me to think of if, then, else, and fi as bash commands, and just like all other commands, they need to be at the start of a line (or after a semi-colon).

Bash: Safe to remove all occurrences of -n inside brackets? [duplicate]

This question already has answers here:
Bash operators: "!" vs "-z"
(2 answers)
Closed 3 years ago.
I recently found this "bug" in my bash script;
if [ "$var" ]; then
echo "Condition is true"
fi
where what I meant was to check if $var is non-empty, i.e. [ -n "$var" ]. As it happens, the code seems to work perfectly fine without the -n. Similarly, I find that I can replace [ -z "$var" ] with [ ! "$var" ].
I tend to like this implicit falseyness (truthiness) based on the (non-)emptyness of a variable in other languages, and so I might adopt this pattern in bash as well. Are there any danger to doing so, i.e. edge cases where the two set of syntaxes are inequivalent?
So we substitute:
-n "$var" -> "$var"
-z "$var" -> ! "$var"
That looks ok and some people do that. There are corner cases where the removal will be harmful and result in syntax errors. These corner cases specially include times, where var is equal to a valid test argument. Ex. var=-n or var="!" etc.
Ex:
$ v1="-n" v2=""; [ -n "$v1" -o -z "$v2" ]; echo $?
0
but
$ v1="-n" v2=""; [ "$v1" -o ! "$v2" ]; echo $?
bash: [: too many arguments
2
That said, I couldn't find a way on my system (bash 5.0.0) to break it without using -o and -a. And anyway, the test man page advises to use && and || instead of -a and -o.
But in POSIX specification test we can find the following application note:
The two commands:
test "$1"
test ! "$1"
could not be used reliably on some historical systems. Unexpected
results would occur if such a string expression were used and $1
expanded to '!', '(', or a known unary primary. Better constructs are:
test -n "$1"
test -z "$1"
So I think as long as you don't use "some historical systems", you are safe. But is it worth the risk? You have to answer yourself.
That said, subjective: I value maintainability and readability much more then saving to type 3 characters and find -n and -z more readable. The intent with -n is clear - test if the string has -nonzero length. The intent with -z is also clear - test if the string has -zero length. I find it confusing to others to write [ "$var" ] and [ ! "$var" ]. At first it would look like $var has some special meaning inside the [ ].
I recently found this "bug" in my bash script
It's not a bug, it's a feature!
First of all, you make use of single brackets. This implies that you are using the test command and not the Bash-builtin function. From the manual :
test EXPRESSION or [ EXPRESSION ]: this exits with the status returned by EXPRESSION
-n STRING: the length of STRING is nonzero.
STRING: equivalent to -n STRING
source: man test
This should answer your question.
Furthermore:
! EXPRESSION: test returns true of EXPRESSION is false
-z STRING: test returns true if the length of STRING is zero.
Which implies that [ ! STRING ] is equivalent to [ -z STRING ].

if statement using "sed" to set variable not working

I'm trying to get an if statement to read the top line of a text file (tmp.txt) which has 0 on the last line. the "then" commands basically go into a directory and run a series of commands for DNA sequence analysis before coming back up, removing the top line of tmp.txt and moving onto the next directory listed in tmp.txt. once it gets to the end of all the listed directories the final line will just be a "0" or perhaps "file-end". The issue is, it's just not working and I can't figure out why. I've swapped out the "then" and "else" commands to make testing a bit easier.
#!bin/bash/sh
value=`(sed -n 1p tmp.txt)`
if ($value -eq 0)
then
echo "I wish i could eat cheese again"
else
echo "theres still more barcodes left"
fi
Parenthesis like that in bash create subshells to run the enclosed commands in. You don't need them at all for the sed command, and they aren't what you're looking for to test values in the if command. Rather than using backticks, the preferred way of running a command and storing its value now adays is $(...) syntax.
For arithmetic tests you can use double parens ((...)), or you can use the [ synonym for test or bash has an extended version [[...]]. With [ especially, a space after the bracket is essential, since you are trying to run the command [ in that case.
Putting those things together we can update your snippet like so:
#!/bin/bash
value=$(sed -n 1p tmp.txt)
if [[ $value -eq 0 ]]
then
echo "I wish i could eat cheese again"
else
echo "theres still more barcodes left"
fi
(Also, I fixed the shebang line to point to /bin/bash, instead of an executable apparently named /bin/bash/sh which likely doesn't exist)

Command online argument in shell script

Hi I have written small shell script, I am not able to understand the behavior of that script. can any one help me to understand that script.
Script:
#!/bin/bash
if [ -z $1 ]
then
echo "fail"
else
echo "success"
fi
While executing the script .
./test.sh one
It exuting the else statement instead of main statement , even though its passing the argument.
can any one explain me this behavior to understand
The -z test in bash is checking if a string is an empty (zero length) value.
Since you're passing an argument to the script $1 is not empty and therefore -z $1 evaluates to false, executing the else portion of your script.
Side note: Since you're working with strings I recommend you to quote variables as follows:
if [ -z "$1" ]; then
echo "String is empty / No argument given"
else
echo "String is not empty / Argument given"
fi
Edit:
As pointed out by user1934428 it's probably better to use [[ instead of [. This, among others, eliminates the need for quoting. See more differences here.
if [[ -z $1 ]]; then
...
However, be aware that this is a bash extension and won't work in sh scripts.

Resources