Loop's regex conditional doesn't work using extended test - bash

I have a loop that evaluates based on a regex conditional:
until read -p "Enter oprator: " operator
[[ $operator =~ ^[+-*\/]$ ]] #doesn't work
do...
The loop will run until the user enters an arithmetic operator (+, -, * or /). When I enter any of those four, the loop still runs.
I've tried variations of this (i.e. place regex in variable, using quotes, grep) but nothing seems to work.

^[+-*\/]$ ]]$
Here problem is placement of an unescaped - in the middle of the bracket expression which acts as a range between + and *.
You may use this regex (no need to escape / in BASH regex):
[[ $operator =~ ^[-+*/]$ ]]
Or even better without regex use glob match:
[[ $operator == [-+*/] ]]

When including the dash or minus sign - in a character class of a Regex, it must be first or last position, or it will be handled like a range marker. Also the slash / does not need escaping with a backslash:
#!/usr/bin/env bash
until
read -r -n1 -p "Enter oprator: " operator
printf \\n
[[ "$operator" =~ [+*/-] ]] #doesn't work
do
printf 'Symbol %q is not an operator!\n' "$operator" >&2
done
POSIX shell grammar implementation:
#!/usr/bin/env sh
until
printf 'Enter oprator: '
read -r operator
printf \\n
[ -n "$operator" ] && [ -z "${operator%%[-+*/]}" ]
do
printf 'Symbol %s is not an operator!\n' "$operator" >&2
done

Related

need to remove the last zeros in line

need to read a file. check for the zeros in the last of each line . if the last digit is zero I want to delete it .please help me for this
input="temp.txt"
while IFS= read -r line
do
echo "output :$line"
if [[ $line == 0$ ]]; then
echo " blash "
else
echo "anotherblash"
fi
done < "$input"
You can do this type of substitution with sed:
sed 's/0*$//' temp.txt
This removes all the trailing zeros from each line. 0* matches "zero or more" 0s, and $ matches the end of the line.
If you only ever want to remove one 0, then remove the *.
If you prefer to do the same thing in the shell (I assume you use bash, since your attempt includes [[), you could do this:
#!/bin/bash
# match any line ending in one or more zeros
# capture everything up to the trailing 0s
re='(.*[^0])0+$'
while read -r line; do
# use =~ for regex match
if [[ $line =~ $re ]]; then
# assign first capture group, discarding trailing 0s
line=${BASH_REMATCH[1]}
fi
echo "$line"
done < temp.txt
But this approach has the disadvantages of being more complicated and less portable, so I would go with the sed option.
In the expression command [[ $line == 0$ ]] you use the regular expression 0$, but, as man sh tells:
When the == and != operators are used, the string to the right
of the operator is considered a pattern and matched according to
the rules described below under Pattern Matching. …
An additional binary operator, =~, is available, with the same
precedence as == and !=. When it is used, the string to the
right of the operator is considered an extended regular expres-
sion and matched accordingly (as in regex(3)).
So, since you use the == operator, you have to specify a pattern as with filename matching, i. e. [[ $line == *0 ]].
While the solution given by John1024 in the comment is the right way to go, if you prefer to follow your original approach, it does not make sense to compare [[ $line == 0$ ]], because this would just check whether the line consists of the digit zero, forllowed by a dollar sign. Instead, you would have to do a regular expression match, i.e.
if [[ $line =~ 0$ ]]
This would yield true, if the line ends in a zero.
Another possibility is to stick with globbing and write the condition as
if [[ $line == *0 ]]
Note that within [[ ... ]], a =~ does regexp matching and a == does wildcard matching (i.e. via globbing).

The escape character `\` in bracket expression

I read such an example which excerpted from an instruction,
and intend to examine \ in ^[-[:alnum:]\._]+$
# is input a valid filename?
read -p "Enter a single item > "
if [[ "$REPLY" =~ ^[-[:alnum:]\._]+$ ]]; then
echo "'$REPLY' is a valid filename."
else
echo "The string '$REPLY' is not a valid filename."
fi
check the bracket expression by feeding some combinations.
$ bash read_validate.sh
Enter a single item > test.tst.
'test.tst.' is a valid filename.'
#test `\`
$ bash read_validate.sh
Enter a single item > test\\tst
The string 'test\tst' is not a valid filename.
When i remove the escape \ from ^[-[:alnum:]\._]+$, to be ^[-[:alnum:]._]+$
$ bash read_validate.sh
Enter a single item > test.tst
'test.tst' is a valid filename.
# to assert that dot is not the any character sign.
$ bash read_validate.sh
Enter a single item > test*tst
The string 'test*tst' is not a valid filename.
# codes run properly.
It seems not necessary to insert escape \ to the pattern.
Is that right?
I cannot make sure if omit some key points about the bracket expression and escape character?
Bash uses Extended Regular Expressions. Quoting the standard:
The special characters '.', '*', '[', and '\' (period, asterisk, left-bracket, and backslash, respectively) shall lose their special meaning within a bracket expression.
So inside [ ], they don't need to be escaped.
The situation is made slightly more complicated by the fact that Bash processes backslashes in your string:
$ set -x
$ [[ '\' =~ [\.] ]] && echo yes
+ [[ \ =~ [.] ]] # look, no backslash!
So the recommended way to use regular expressions is to set a shell variable:
$ re='[\.]'
+ re='[\.]'
$ [[ '\' =~ $re ]] && echo yes
+ [[ \ =~ [\.] ]] # backslash preserved!
+ echo yes
yes

multiplicator operation bash issue

from stdin I read string and if it's like this:
"numberOne * numberTwo"
I have to execute the multiplicator between numberOne and numberTwo.
This is my code:
read string
regex2="^[1-9]+ \*{1,1} [1-9]+$"
if [[ $string =~ $regex2 ]]; then
val=1
val1=`echo $string|cut -d " " -f 1`
val2=`echo $string|cut -d " " -f 3`
((val=$val1*$val2))#comment
echo $val
fi
but I get two errors:
1) on the line where calculate the operation ((val=$val1*$val2)), it says syntax error : arithmetic operator invalid
2) where , by shell , I insert the input string, for example 3 * 2 on shell it prints a list of files, then I thought it was for jolly character "*", and for this reason I substuited the input string with this:
3 \* 2
but the result doesn't change
Always, always quote your expansions.
echo $string, when $string contains a * surrounded by whitespace, treats that * as a glob, replacing it with a list of filenames in the current directory. Your filenames are not likely to be part of a legitimate math operation.
Use echo "$string" instead, if you must use echo at all; printf '%s\n' "$string" is the alternative that works in corner cases where echo fails (and/or behaves in ways unspecified by POSIX).
That said, there's no legitimate reason to use cut here at all; your regex will split your string into pieces perfectly well on its own.
regex2='^([1-9][0-9]*) [*] ([1-9][0-9]*)$'
read -r string
if [[ $string =~ $regex2 ]]; then
val=$(( ${BASH_REMATCH[1]} * ${BASH_REMATCH[2]} ))
echo "$val"
fi
...and even if you couldn't do that, it would be a better practice to use read:
read val1 _ val2 <<<"$string"
echo "$(( val1 * val2 ))"

check for string format in bash script

I am attempting to check for proper formatting at the start of a string in a bash script.
The expected format is like the below where the string must always begin with "ABCDEFG-" (exact letters and order) and the numbers would vary but be at least 3 digits. Everything after the 3rd digit is a do not care.
Expected start of string: "ABCDEFG-1234"
I am using the below code snippet.
[ $(echo "$str" | grep -E "ABCDEFG-[0-9][0-9][0-9]") ] && echo "yes"
str1 = "ABCDEFG-1234"
str2 = "ABCDEFG-1234 - Some more text"
When I use str1 in place of str everything works ok and yes is printed.
When I use str2 in place of str i get the below error
[: ABCDEFG-1234: unary operator expected
I am pretty new to working with bash scripts so any help would be appreciated.
If this is bash, you have no reason to use grep for this at all; the shell has built-in regular expression support.
re="ABCDEFG-[0-9][0-9][0-9]"
[[ $str =~ $re ]] && echo "yes"
That said, you might want your regex to be anchored if you want a match in the beginning rather than anywhere in the content:
re="^ABCDEFG-[0-9][0-9][0-9]"
[[ $str =~ $re ]] && echo "yes"
That said, this doesn't need to be an ERE at all -- a glob-style pattern match would also be adequate:
if [[ $str = ABCDEFG-[0-9][0-9][0-9]* ]]; then echo "yes"; fi
Try grep -E "ABCDEFG-[0-9][0-9][0-9].*"

How to get first character of variable

I'm trying to get the first character of a variable, but I'm getting a Bad substitution error. Can anyone help me fix it?
code is:
while IFS=$'\n' read line
do
if [ ! ${line:0:1} == "#"] # Error on this line
then
eval echo "$line"
eval createSymlink $line
fi
done < /some/file.txt
Am I doing something wrong or is there a better way of doing this?
-- EDIT --
As requested - here's some sample input which is stored in /some/file.txt
$MOZ_HOME/mobile/android/chrome/content/browser.js
$MOZ_HOME/mobile/android/locales/en-US/chrome/browser.properties
$MOZ_HOME/mobile/android/components/ContentPermissionPrompt.js
To get the first character of a variable you need to say:
v="hello"
$ echo "${v:0:1}"
h
However, your code has a syntax error:
[ ! ${line:0:1} == "#"]
# ^-- missing space
So this can do the trick:
$ a="123456"
$ [ ! "${a:0:1}" == "#" ] && echo "doesnt start with #"
doesnt start with #
$ a="#123456"
$ [ ! "${a:0:1}" == "#" ] && echo "doesnt start with #"
$
Also it can be done like this:
$ a="#123456"
$ [ "$(expr substr $a 1 1)" != "#" ] && echo "does not start with #"
$
$ a="123456"
$ [ "$(expr substr $a 1 1)" != "#" ] && echo "does not start with #"
does not start with #
Update
Based on your update, this works to me:
while IFS=$'\n' read line
do
echo $line
if [ ! "${line:0:1}" == "#" ] # Error on this line
then
eval echo "$line"
eval createSymlink $line
fi
done < file
Adding the missing space (as suggested in fedorqui's answer ;) ) works for me.
An alternative method/syntax
Here's what I would do in Bash if I want to check the first character of a string
if [[ $line != "#"* ]]
On the right hand side of ==, the quoted part is treated literally whereas * is a wildcard for any sequence of character.
For more information, see the last part of Conditional Constructs of Bash reference manual:
When the ‘==’ and ‘!=’ operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching
Checking that you're using the right shell
If you are getting errors such as "Bad substitution error" and "[[: not found" (see comment) even though your syntax is fine (and works fine for others), it might indicate that you are using the wrong shell (i.e. not Bash).
So to make sure you are using Bash to run the script, either
make the script executable and use an appropriate shebang e.g. #!/bin/bash
or execute it via bash my_script
Also note that sh is not necessarily bash, sometimes it can be dash (e.g. in Ubuntu) or just plain ol' Bourne shell.
Try this:
while IFS=$'\n' read line
do
if ! [ "${line:0:1}" = "#" ]; then
eval echo "$line"
eval createSymlink $line
fi
done < /some/file.txt
or you can use the following for your if syntax:
if [[ ! ${line:0:1} == "#" ]]; then
TIMTOWTDI ^^
while IFS='' read -r line
do
case "${line}" in
"#"*) echo "${line}"
;;
*) createSymlink ${line}
;;
esac
done < /some/file.txt
Note: I dropped the eval, which could be needed in some (rare!) cases (and are dangerous usually).
Note2: I added a "safer" IFS & read (-r, raw) but you can revert to your own if it is better suited. Note that it still reads line by line.
Note3: I took the habit of using always ${var} instead of $var ... works for me (easy to find out vars in complex text, and easy to see where they begin and end at all times) but not necessary here.
Note4: you can also change the test to : *"#"*) if some of the (comments?) lines can have spaces or tabs before the '#' (and none of the symlink lines does contain a '#')

Resources