How to test a dynamic substring variable in a conditional in shellscript? - bash

This code check if the last 4 characters of the variable $1 correspond to the string in variable $2 anticipated by a dot.
if [ "${1: -4}" == ".$2" ]; then
return 0
else
return 1
fi
// true with $1 = example.doc and $2 = doc
// false with $1 = example.docx and $2 = doc
how can I replace the hardcoded 4 with the following variable $CHECKER_EXTENSION calculated like this?
LENGTH_EXTENSION=${#2}
CHECKER_EXTENSION=$(( LENGTH_EXTENSION + 1 ))
Thanks in advance

You don't need to strip the leading characters from $1, since bash's [[ ]] can do wildard-style pattern matching:
if [[ "$1" = *".$2" ]]; then
...
Note that you must use [[ ]], and not [ ], to get pattern-matching rather than simple string equality testing. Also, having the * unquoted but .$2 in quotes means the * will be treated as a wildcard, but $2 will be matched literally even if it contains wildcardish characters. If you want $2 to also be treated as a pattern (e.g. you could use [Jj][Pp][Gg] to match "jpg" and "JPG" and combinations), leave off the quotes:
if [[ "$1" = *.$2 ]]; then
Oh, and the quotes around $1 don't matter in this particular situation; but I tend to double-quote variables unless there's a specific reason not to.

The offset is interpreted as an arithmetic expression (the same syntax as inside $(( ... ))), so you can write:
if [ "${1: -CHECKER_EXTENSION}" == ".$2" ]; then
You can even eliminate the CHECKER_EXTENSION variable and write:
if [ "${1: -(${#2} + 1)}" == ".$2" ]; then

You may use it like this:
myfunc() {
local num_ext=$(( ${#2} + 1 ))
[[ "${1: -$num_ext}" = ".$2" ]]
}

If your execution environment meets the following criteria, you can choose a more concise method.
The requirement is to check if the extension is as expected
I'm using Bash
#!/usr/bin/env bash
if [[ ${1##*.} == "${2}" ]]; then
echo "same"
else
echo "not same"
fi
Execute test.
# test case 1
$ ./sample.sh test.txt txt
same
# test case 2
$ ./sample.sh test.exe txt
not same
# test case 3
$ ./sample.sh test.exe.txt txt
same
# test case 4
$ ./sample.sh test.txt.exe txt
not same
# test case 5
$ ./sample.sh test.txt.exe exe
same

Related

bash if "$1" == "0" is always false when running function for bash prompt

I have been struggling with this for a long time.
Trying to change colour as part of my prompt depending on the exit code of the last command.
I have reduced my prompt to a minimal example:
Red="\[\033[31m\]"
Green="\[\033[32m\]"
Reset="\[\033[0m\]"
statColour(){
if [[ "$1" == "0" ]]; then
echo -e "${Green} $1 "
else
echo -e "${Red} $1 "
fi
}
export PS1="$(statColour \$?)What Colour? $Reset"
And results in red always being used despite the fact the number is clearly 0 in the first instance.
I have tried [ and $1 -eq 0 with no success. Why isn't this working?
Try this:
Red="\033[35m"
Green="\033[32m"
Reset="\033[0m"
statColour(){
if [[ $1 = 0 ]]; then
echo -e "${Green} $1 "
else
echo -e "${Red} $1 "
fi
}
export PS1="\$(statColour \$?)What Colour? $Reset"
# ^
Color definitions changed
Call of statColour is now done every time, and not only once.
if [[ ]] optimized
For an explanation why you always take the false branch:
You are calling statColour with \$? as argument. The backslash ensures, that the $ is taken literally (and not as the start of a parameter expanson), so you have in effect the literal string $?. Since ? is a wildcard character, it is undergoing filename generation, i.e. the parameter is replaced by all files where the name is a $, followed by a single character. If there are no such files in your directory (which is probably the case), the string $? is passed literally to statColour.
Inside statColour, you wrote
[[ "$1" == "0" ]]
which means that you ask, whether the string $? is equal to the string 0. This is never the case, hence the comparision is always false.
For your problem, you could try this approach (not tested, so you may have to debug it a bit):
statColour() {
# Fetch the exit code of the last program
local last_exit_code=$?
if ((last_exit_code == 0)) # Numeric comparision
then
.....
else
...
fi
# Preserve the exit code
return $last_exit_code
}
and set the prompt as
PS1='$(statColour) '"$Reset"
The single quotes ensure that statColour is evaluated dynamically, while $Reset is in double quotes since it is OK to evaluate it statically.

BASH regex syntax for replacing a sub-string

I'm working in bash and I want to remove a substring from a string, I use grep to detect the string and that works as I want, my if conditions are true, I can test them in other tools and they select exactly the string element I want.
When it comes to removing the element from the string I'm having difficulty.
I want to remove something like ": Series 1", where there could be different numbers including 0 padded, a lower case s or extra spaces.
temp='Testing: This is a test: Series 1'
echo "A. "$temp
if echo "$temp" | grep -q -i ":[ ]*[S|s]eries[ ]*[0-9]*" && [ "$temp" != "" ]; then
title=$temp
echo "B. "$title
temp=${title//:[ ]*[S|s]eries[ ]*[0-9]*/ }
echo "C. "$temp
fi
# I trim temp for spaces here
series_title=${temp// /_}
echo "D. "$series_title
The problem I have is that at points C & D
Give me:
C. Testing
D. Testing_
You can perform regex matching from bash alone without using external tools.
It's not clear what your requirement is. But from your code, I guess following will help.
temp='Testing: This is a test: Series 1'
# Following will do a regex match and extract necessary parts
# i.e. extract everything before `:` if the entire pattern is matched
[[ $temp =~ (.*):\ *[Ss]eries\ *[0-9]* ]] || { echo "regex match failed"; exit; }
# now you can use the extracted groups as follows
echo "${BASH_REMATCH[1]}" # Output = Testing: This is a test
As mentioned in the comments, if you need to extract parts both before and after the removed section,
temp='Testing: This is a test: Series 1 <keep this>'
[[ $temp =~ (.*):\ *[Ss]eries\ *[0-9]*\ *(.*) ]] || { echo "invalid"; exit; }
echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]}" # Output = Testing: This is a test <keep this>
Keep in mind that [0-9]* will match zero lengths too. If you need to force that there need to be at least single digit, use [0-9]+ instead. Same goes for <space here>* (i.e. zero or more spaces) and others.

How can I check if variable equal to other variable apart of (maybe) the last letter?

Given the following variable:
var=abcd
What I need to write in the if so that I will get true if I will compare var with other variables so that they with beginning of abcd but maybe they with (at most) more a letter (in the last) ?
for example:
I want the following cases will give me TRUE:
var in related to abcd.
var in related to abcde.
var in related to abcdk.
And the following cases will give me FALSE:
var in related to abc.
var in related to abcdee.
With pure POSIX, you can check if the variable itself is equal to abcd, or if the variable minus its last letter is equal.
if [ "$var" = abcd ] || [ "${var%?}" = abcd ]; then
Using bash's [[ ... ]], you can use either extended pattern matching
if [[ $var = abcd#(|?) ]]; then
(where #(|?) matches exactly one of the empty string or a single arbitrary character)
or a regular expression match
if [[ $var ~= ^abcd.?$ ]]; then
(where .? matches a single optional character. ^ and $ match the beginning and end, respectively, of the string, limiting the length of the match).
The [[ command in Bash does pattern matching on strings by default, so you can use ?, * and some other metacharacters for matching one (any, but exactly one) with ?, zero or more characters with *, etc. For example:
$ [[ abcde == abcd? ]] && echo yes || echo no
yes
$ [[ abcdefg == abcd* ]] && echo yes || echo no
yes
$ [[ abc == abcd? ]] && echo yes || echo no
no
$ [[ abcd == abcd? ]] && echo yes || echo no
no
Note that the value is on the left, and the pattern on the right. Also, [[ supports the use of logical operators like || and &&, so you can combine it like:
ref=abcd
val=abcde
if [[ $val == $ref || $val == $ref? ]]; then
# match
fi
You have an additional option using string indexes in bash. You can simply check whether the first 4 letters of a variable match abcd and check whether the variable length is greater than 5, e.g.
if [[ abcd = ${var:0:4} ]] && [[ ${#var} -le 5 ]]; then
## condition true
fi
You have a number of options in bash. You can even make the testing above generic by storing your test string in a variable, and then using the length in place of each of the number above, e.g.
test_string="abcd"
len=${#test_string}
if [[ $test_string = ${var:0:$((len))} ]] && [[ ${#var} -le $((len+1)) ]]; then
## condition true
fi

bash verbose string comparison slashes

I am comparing two strings in a bash script as follows:
x="hello"
y="hello"
if [[ "$x" != "$y" ]]; then
echo "different"
else
echo "same"
fi
This comparison works. When I execute the script with -x, the comparison still works, but it shows the output
+ x=hello
+ y=hello
+ [[ -n hello ]]
+ [[ hello != \h\e\l\l\o ]]
+ echo same
I'm curious why the right side of the string shows as\h\e\l\l\o and not hello
The simple explanation is for the same reason that the left-hand side doesn't have quotes around it.
-x is showing you an equivalent but not exact representation of what it ran. The right-hand side of = and != in [[ ... ]] is matched as a pattern.
From the 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 under Pattern Matching. .... Any part of the pattern may be quoted to force it to be matched as a string.
The -x output, for some reason, chooses to use escaping instead quoting to disable pattern matching there.
When using =, ==, and != in [[, the right-side string can contain globs (*, ?, etc.).
The backslashes in your example aren't necessary, though they don't hurt. They are needed if the right-side string contains a possible wildcard character. For example:
$ set -x
$ [[ hi == 'hi*' ]]; echo $?
+ [[ hi == \h\i\* ]]
+ echo 1
1
$ [[ hi == hi* ]]; echo $?
+ [[ hi == hi* ]]
+ echo 0
0

How do I compare two strings in if condition in bash

s="STP=20"
if [[ "$x" == *"$s"* ]]
The if condition is always false; why?
Try this: http://tldp.org/LDP/abs/html/comparison-ops.html
string comparison
=
is equal to
if [ "$a" = "$b" ]
There is a difference in testing for equality between [ ... ] and [[ ... ]].
The [ ... ] is an alias to the test command:
STRING1 = STRING2 the strings are equal
However, when using [[ ... ]]
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. If the shell option nocasematch is enabled, the match is performed without regard to the case of alphabetic characters. The return value is 0 if the string matches (==) or does not match (!=) the pattern, and 1 otherwise. Any part of the pattern may be quoted to force it to be matched as a string.
The same seems to be true with just the = sign:
$ foo=bar
$ if [[ $foo = *ar ]]
> then
> echo "These patterns match"
> else
> echo "These two strings aren't equal"
> fi
These patterns match
Note the difference:
$ foo=bar
> if [ $foo = *ar ]
> then
> echo "These patterns match"
> else
> echo "These two strings aren't equal"
> fi
These two strings aren't equal
However, there are a few traps with the [ $f00 = *ar ] syntax. This is the same as:
test $foo = *ar
Which means the shell will interpolate glob expressions and variables before executing the statement. If $foo is empty, the command will become equivalent to:
test = *ar # or [ = *ar ]
Since the = isn't a valid comparison operator in test, you'll get an error like:
bash: [: =: unary operator expected
Which means the [ was expecting a parameter found in the test manpage.
And, if I happen to have a file bar in my directory, the shell will replace *ar with all files that match that pattern (in this case bar), so the command will become:
[ $foo = bar ]
which IS true.
To get around the various issues with [ ... ], you should always put quotes around the parameters. This will prevent the shell from interpolating globs and will help with variables that have no values:
[ "$foo" = "*ar" ]
This will test whether the variable $foo is equal to the string *ar. It will work even if $foo is empty because the quotation marks will force an empty string comparison. The quotes around *ar will prevent the shell from interpolating the glob. This is a true equality.
Of course, it just so happens that if you use quotation marks when using [[ ... ]], you'll force a string match too:
foo=bar
if [[ $foo == "*ar" ]]
then
echo "This is a pattern match"
else
echo "These strings don't match"
fi
So, in the end, if you want to test for string equality, you can use either [ ... ] or [[ ... ]], but you must quote your parameters. If you want to do glob pattern matching, you must leave off the quotes, and use [[ ... ]].
To compare two strings in variables x and y for equality, use
if test "$x" = "$y"; then
printf '%s\n' "equal"
else
printf '%s\n' "not equal"
fi
To test whether x appears somewhere in y, use
case $y in
(*"$x"*)
printf '%s\n' "$y contains $x"
;;
(*)
printf '%s\n' "$y does not contain $x"
;;
esac
Note that these constructs are portable to any POSIX shell, not just bash. The [[ ]] construct for tests is not (yet) a standard shell feature.
I do not know where you came up with the *, but you were real close:
s="STP=20"
if [[ "STP=20" == "$s" ]]; then
echo "It worked!"
fi
You need to escape = using \ in the string s="STP=20"
s="STP\=20"
if [[ "STP\=20" == "$s" ]]; then echo Hi; else echo Bye; fi

Resources