Case insensitive comparision in If condition - bash

I have this csv file and i need to count the number of rows which satisfies the condition that the row entry is betwen a certain year range and the artist_name matches the name argument. But the string matching should be case insensitive. How do i achieve that in the if loop..
I am a beginner, so please bear with me
#!/bin/bash
file="$1"
artist="$2"
from_year="$(($3-1))"
to_year="$(($4+1))"
count=0
while IFS="," read arr1 arr2 arr3 arr4 arr5 arr6 arr7 arr8 arr9 arr10 arr11 ; do
if [[ $arr11 -gt $from_year ]] && [[ $arr11 -lt $to_year ]] && [[ $arr7 =~ $artist ]]; then
count=$((count+1))
fi
done < "$file"
echo $count
The $arr7 =~ $artist part is where i need to make the modification

Bash has a builtin method for converting strings to lower case. Once they are both lower case, you can compare them for equality. For example:
$ arr7="Rolling Stones"
$ artist="rolling stoneS"
$ [ "${arr7,,}" = "${artist,,}" ] && echo "Matches!"
Matches!
$ [[ ${arr7,,} =~ ${artist,,} ]] && echo "Matches!"
Matches!
Details
${parameter,,} converts all characters in a string to lower case. If you wanted to convert to upper case, use ${parameter^^}. If you want to convert just some of the characters, use ${parameter,,pattern} where only those characters matching pattern are changed. Still more details on this are documented by manbash`:
${parameter^pattern}
${parameter^^pattern}
${parameter,pattern}
${parameter,,pattern}
Case modification. This expansion modifies the case of alphabetic characters in parameter. The pattern is expanded to
produce a pattern just
as in pathname expansion. The ^ operator converts lowercase letters matching pattern to uppercase; the , operator
converts matching uppercase
letters to lowercase. The ^^ and ,, expansions convert each matched character in the expanded value; the ^ and , expansions
match and convert
only the first character in the expanded value. If pattern is omitted, it is treated like a ?, which matches every
character. If parameter
is # or *, the case modification operation is applied to each positional parameter in turn, and the expansion is the
resultant list. If
parameter is an array variable subscripted with # or *, the case modification operation is applied to each member of the array
in turn, and
the expansion is the resultant list.
Compatibility
These case modification methods require bash version 4 (released on 2009-Feb-20) or better.

The bash case-transformations (${var,,} and ${var^^}) were introduced (some time ago) in bash version 4. However, if you are using Mac OS X, you most likely have bash v3.2 which doesn't implement case-transformation natively.
In that case, you can do lower-cased comparison less efficiently and with a lot more typing using tr:
if [[ $(tr "[:upper:]" "[:lower:]" <<<"$arr7") = $(tr "[:upper:]" "[:lower:]" <<<"$artist") ]]; then
# ...
fi
By the way, =~ does a regular expression comparison, not a string comparison. You almost certainly wanted =. Also, instead of [[ $x -lt $y ]] you can use an arithmetic compound command: (( x < y )). (In arithmetic expansions, it is not necessary to use $ to indicate variables.)

Use shopt -s nocasematch
demo
#!/bin/bash
words=(Cat dog mouse cattle scatter)
#Print words from list that match pat
print_matches()
{
pat=$1
echo "Pattern to match is '$pat'"
for w in "${words[#]}"
do
[[ $w =~ $pat ]] && echo "$w"
done
echo
}
echo -e "Wordlist: (${words[#]})\n"
echo "Normal matching"
print_matches 'cat'
print_matches 'Cat'
echo -e "-------------------\n"
echo "Case-insensitive matching"
shopt -s nocasematch
print_matches 'cat'
print_matches 'CAT'
echo -e "-------------------\n"
echo "Back to normal matching"
shopt -u nocasematch
print_matches 'cat'
output
Wordlist: (Cat dog mouse cattle scatter)
Normal matching
Pattern to match is 'cat'
cattle
scatter
Pattern to match is 'Cat'
Cat
-------------------
Case-insensitive matching
Pattern to match is 'cat'
Cat
cattle
scatter
Pattern to match is 'CAT'
Cat
cattle
scatter
-------------------
Back to normal matching
Pattern to match is 'cat'
cattle
scatter

Related

In Bash, is it possible to match a string variable containing wildcards to another string

I am trying to compare strings against a list of other strings read from a file.
However some of the strings in the file contain wildcard characters (both ? and *) which need to be taken into account when matching.
I am probably missing something but I am unable to see how to do it
Eg.
I have strings from file in an array which could be anything alphanumeric (and include commas and full stops) with wildcards : (a?cd, xy, q?hz, j,h-??)
and I have another string I wish to compare with each item in the list in turn. Any of the strings may contain spaces.
so what I want is something like
teststring="abcdx.rubb ish,y"
matchstrings=("a?cd" "*x*y" "q?h*z" "j*,h-??")
for i in "${matchstrings[#]}" ; do
if [[ "$i" == "$teststring" ]]; then # this test here is the problem
<do something>
else
<do something else>
fi
done
This should match on the second "matchstring" but not any others
Any help appreciated
Yes; you just have the two operands to == reversed; the glob goes on the right (and must not be quoted):
if [[ $teststring == $i ]]; then
Example:
$ i=f*
$ [[ foo == $i ]] && echo pattern match
pattern match
If you quote the parameter expansion, the operation is treated as a literal string comparison, not a pattern match.
$ [[ foo == "$i" ]] || echo "foo != f*"
foo != f*
Spaces in the pattern are not a problem:
$ i="foo b*"
$ [[ "foo bar" == $i ]] && echo pattern match
pattern match
You can do this even completely within POSIX, since case alternatives undergo parameter substitution:
#!/bin/sh
teststring="abcdx.rubbish,y"
while IFS= read -r matchstring; do
case $teststring in
($matchstring) echo "$matchstring";;
esac
done << "EOF"
a?cd
*x*y
q?h*z
j*,h-??
EOF
This outputs only *x*y as desired.

How to check of the user input value is Upper case, Lower case or a digit using Shell Script?

I am trying to write a shell script to read user input data and check if the input value is either Upper Case, Lower Case or anything else. But what I wrote is only checking a single character
Here is what I wrote:
printf 'Please enter a character: '
IFS= read -r c
case $c in
([[:lower:]]) echo lowercase letter;;
([[:upper:]]) echo uppercase letter;;
([[:alpha:]]) echo neither lower nor uppercase letter;;
([[:digit:]]) echo decimal digit;;
(?) echo any other single character;;
("") echo nothing;;
(*) echo anything else;;
esac
How can I make it read a long String other than a single character and get the output accordingly?
You can do it in many ways, here you have one:
#!/bin/bash
read -p "Enter something: " str
echo "Your input is: $str"
strUppercase=$(printf '%s\n' "$str" | awk '{ print toupper($0) }')
strLowercase=$(printf '%s\n' "$str" | awk '{ print tolower($0) }')
if [ -z "${str//[0-9]}" ]
then
echo "Digit"
elif [ $str == $strLowercase ]
then
echo "Lowercase"
elif [ $str == $strUppercase ]
then
echo "Uppercase"
else
echo "Something else"
fi
Preceding your use with shopt -s extglob, you can use +([[:upper:]]) to match a string composed of one or more uppercase letters.
From man 1 bash:
If the extglob shell option is enabled using the shopt builtin, several
extended pattern matching operators are recognized. In the following
description, a pattern-list is a list of one or more patterns separated
by a |. Composite patterns may be formed using one or more of the fol‐
lowing sub-patterns:
?(pattern-list)
Matches zero or one occurrence of the given patterns
*(pattern-list)
Matches zero or more occurrences of the given patterns
+(pattern-list)
Matches one or more occurrences of the given patterns
#(pattern-list)
Matches one of the given patterns
!(pattern-list)
Matches anything except one of the given patterns
Use, for example, +([[:upper:][:digit:] .]) to match one or more {uppercase letters, digits, spaces, dots}. Consider using some of the other following classes defined in the POSIX standard:
alnum alpha ascii blank cntrl digit graph lower print punct space upper word xdigit
Proof (just a test on an example) that it works:
shopt -s extglob; case "1A5. .Q7." in (+([[:upper:][:digit:] .])) echo "it works";; esac

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).

Matching a string against contents of an array with regex operator not working

i make a simply bash script to change number version based on the source branch of a merge request, i need increment different value if a feature or a hotfix/bigfix/fix branches names:
#!/bin/bash
if [ $# -eq 0 ]
then
echo -e "\nUsage: $0 MERGE_REQUEST_SOURCE\n"
exit 1
fi
if [ ! -f version ]; then
echo "0.0.0" > version
fi
VERSION=$(cat version)
MERGE_REQUEST_SOURCE=$1
declare -a FEATURE_LIST=("feature")
declare -a HOTFIX_LIST=("fix" "hotfix" "bugfix")
IFS="."
read -a num <<< ${VERSION}
MAJOR=${num[0]}
FEATURE=${num[1]}
HOTFIX=${num[2]}
if [[ ${MERGE_REQUEST_SOURCE} =~ .*${FEATURE_LIST[*]}.* ]]; then
FEATURE=$((${FEATURE}+1))
echo "${MAJOR}.${FEATURE}.${HOTFIX}" > version
elif [[ ${MERGE_REQUEST_SOURCE} =~ .*${HOTFIX_LIST[*]}.* ]]; then
HOTFIX=$((${HOTFIX}+1))
echo "${MAJOR}.${FEATURE}.${HOTFIX}" > version
else
echo -e "Nothing change, exit."
exit 0
fi
I've declared two arrays, FEATURE_LIST that contain only feature and work, if i type ./script.sh feature or ./script.sh feature/foobar it increase the value, instead if i type ./script.sh hotfix or other values combinations of array HOTFIX_LIST nothing happened. Where the error?
Using .*${HOTFIX_LIST[*]}.* is quite a tedious way of representing a string for an alternate match for the regex operator in bash. You can use the | character to represent alternations (because Extended Regular Expressions library is supported) in bash regex operator.
First generate the alternation string from the array into a string
hotfixList=$(IFS="|"; printf '^(%s)$' "${HOTFIX_LIST[*]}")
echo "$hotfixList"
^(fix|hotfix|bugfix)$
The string now represents a regex pattern comprising of three words that will match exactly as is because of the anchors ^ and $.
You can now use this variable in your regex match
[[ ${MERGE_REQUEST_SOURCE} =~ $hotfixList ]]
also for the feature check, just put the whole array expansion with [*] on the RHS which would be sufficient. Also you don't need the greedy matches, since you have the longer string on the LHS the comparison would still hold good.
[[ ${MERGE_REQUEST_SOURCE} =~ ${FEATURE_LIST[*]} ]]
As a side note, always use lower case variable names for user variables. The uppercase names are reserved only for the variables maintained by the shell which are persistent and have special meaning.

Counting filenames matching a regex in bash

I have the following script
setup=`ls ./test | egrep 'm-ha-.........js'`
regex="m-ha-(........)\.js"
if [[ "$setup" =~ $regex ]]
then
checksum=${BASH_REMATCH[1]}
fi
I noticed that if [[ "$setup" =~ $regex ]] returns the first file that matches the regex in BATCH_REMATCH.
Is there a way to test how many files matches the regex? I want to return an error, if there are multiple files that matches the regex.
You don't need a regex, or ls, for this.
matches=(./test/m-ha-????????.js)
[[ ${#matches[*]} -gt 1 ]] && echo "More than one."
We expand the wildcard into an array and examine the number of elements in the array.
If you want to strip the prefix, ${match[0]#mh-a-} returns the first element with the prefix removed. The % interpolation operator similarly strips a suffix, e.g. ${match[0]%.js}. You can't strip from both ends at the same time, but you can loop over the matches:
for match in "${matches[#]%.js}"; do
echo "${match#./test/m-ha-}"
done
Notice that the array won't be empty if there are no matches unless you explicitly set the nullglob option.

Resources