ksh + smart test line solution - shell

I
write the following syntax (part of my ksh script)
to check if the first word of LINE=star
and the second word is car
[[ ` echo $LINE | awk '{print $1}' ` = star ]] && [[ ` echo $LINE | awk '{print $2}' ` = car ]] && print "match"
I need other simple smart and shorter solution then my syntax.
from awk,perl or sed(if it possible to use echo only once or better if we cant not use echo, because I need to save time to minimum)
can I get some ideas?

you don't actually need to call any external tools
set -f
set -- $LINE
case "$1 $2" in
"star car")
echo "match";;
esac

You don't need an external process at all:
[[ $LINE == *([ ])star+([ ])car?([ ]*) ]]
If you also need to extract the first word sometimes (warning, typed directly into the browser):
LINE=${LINE##+[ ]} # strip leading spaces
first_word=${LINE%%[ ]*}
if [[ $LINE = *[ ]* ]]; then
LINE_minus_first_word=${LINE##*+([ ])}
else
LINE_minus_first_word=''
fi
Add a tab inside the brackets if they may appear in $LINE.

set -o noglob
words=($LINE)
[[ "${words[0]} ${words[1]}" == "star car" ]] && print "match"
Edit:
Using ksh and Bash's regex matching (Bash >= version 3.2):
pattern='^[[:blank:]]*star[[:blank:]]+car([[:blank:]]+|$)'
[[ $LINE =~ $pattern ]] && print "match"

You can do:
[[ ` echo $LINE | awk '{printf("%s:%s",$1,$2)}'` = star:car ]] && print "match"

Related

Match if variable has WORD repeated more than once

I have this variable:
>echo $br_name
srxa wan-a1 br-wan3-xa1 0A:AA:DD:C1:F1:A3 ge-0.0.3 srxa wan-a2 br-wan3-xa2 0A:AA:DD:C1:F2:A3 ge-0.0.3
I am trying to create a conditional where it detects whether ge-0.0.3 is repeated more than 1 time in my variable $br_name
For example:
if [[ $br_name has ge-0.0.3 repeated more than one time ]]
then
echo "ge-0.0.3 is shown more than once"
else
:
fi
Bash's =~ is using extended RE.
[Bash-5.2] % check() { local s='(ge-0\.0\.3.*){2,}'; [[ "$1" =~ $s ]] && echo yes || echo no; }
[Bash-5.2] % check 'xxx'
no
[Bash-5.2] % check 'ge-0.0.3'
no
[Bash-5.2] % check 'ge-0.0.3 ge-0.0.3 '
yes
[Bash-5.2] % check 'ge-0.0.3 ge-0.0.3 ge-0.0.3 '
yes
You can use grep -o to print only the matched phrase. Also use -F to make sure that it matches literal characters instead of a regex where . and - are special
if [[ $(echo "$br_name" | grep -Fo ge-0.0.3 | wc -l) -gt 1 ]]; then
echo "ge-0.0.3 is shown more than once"
else
echo "only once"
fi
For more complex patterns of course you can drop -F and write a proper regex for grep
simple word
If your word would be "easy", you can detect the occurrences count with:
echo "123 123 123" | sed "s/123 /123\n/g" | wc -l
In which the word is replace for the same but with \n and then wc count the lines
or you can try one of these:
Count occurrences of a char in a string using Bash
How to count number of words from String using shell
complex
Since your word is "complex" or you will want a pattern, you will need regex:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
https://linuxconfig.org/advanced-bash-regex-with-examples
script.sh
count=0
for word in $1; do
if [[ "$word" =~ .*ge-0\.0\.3.* ]]
then
count=$(($count+1))
fi
done
if [ "$count" -gt "1" ];
then
echo "ge-0.0.3 is shown more than once"
else
if [ "$count" -eq "0" ];
then
echo "ge-0.0.3 is not shown"
else
echo "ge-0.0.3 is shown once"
fi
fi
execution
bash script.sh "srxa wan-a1 br-wan3-xa1 0A:AA:DD:C1:F1:A3 ge-0.0.3 srxa wan-a2 br-wan3-xa2 0A:AA:DD:C1:F2:A3 ge-0.0.3"
grep
With grep you can get the ocurrence count
ocurrences=( $(grep -oE '(ge-0\.0\.3)' <<<$1) )
ocurrences_count=${#ocurrences[*]}
echo $ocurrences_count
Assuming you want to do a literal string match on whole words, not substrings, then this might be what you want:
$ if (( $(grep -oFw 'ge-0.0.3' <<<"$br_name" | wc -l) > 1 )); then echo 'yes'; else echo 'no'; fi
yes

Shell - Skip matching lines while read

I'm looking for help with my shell... Hope I'll find it here...
Here's my code :
#!/bin/sh
while IFS= read -r line || [ -n "$line" ]
do
[[ "$line" =~ ^[[:space:]]*\# ]] && continue # This line must stay
[[ "$line" =~ *read[[:space:]]-r[[:space:]]line* ]] && continue
echo "${line%$NL}"
done < $0
First test will suppress "only comment lines".
Second test purpose is to suppress the "while IFS= read..." lines - no matter what, it's just a test :-)
"done < $0" has been here written intentionaly... for the test !
Running the shell outputs this :
while IFS= read -r line || [ -n "$line" ]
do
[[ "$line" =~ ^[[:space:]]*\# ]] && continue # This line must stay
[[ "$line" =~ *read[[:space:]]-r[[:space:]]line* ]] && continue
echo "${line%$NL}"
done < $0
as I thought the first line will be gone because of matching then 2nd test.
What's my mistake ?
For the record, I don't want to use extra sed or awk sentence.
Actually, the input data (here $0 file) has to be standard input (eg extract from tee command). I read lot of stackOverflow subject about this, with sed or awk responses that didn't match my purpose.
The regex is invalid. A short test shows:
> [[ 'while IFS= read -r line || [ -n "$line" ]' =~ *read[[:space:]]-r[[:space:]]line* ]]
> echo $?
2
The * can't be "alone" - it can't be the first character in a POSIX extended regular expression. It has to "bind" to something, ex. a dot .. A dot represents any character. You want:
[[ $line =~ .*read[[:space:]]-r[[:space:]]line.* ]]
Problem is presence of starting quantifier * in the regex in second continue line. You may use:
[[ "$line" =~ read[[:space:]]+-r[[:space:]]+line ]] && continue
There is no need to match anything before read or after line in this regex.
Also it is better to use quantifier + after [[:space:]] to make it match 1 or more white spaces.
You can do more refactoring and combine both regex into one by using alternation as in this code:
while IFS= read -r line || [[ -n $line ]]
do
[[ $line =~ ^[[:space:]]*#|read[[:space:]]+-r[[:space:]]+line ]] && continue
echo "${line%$NL}"
done < $0

How can I check if a variable is contains only letters

I tried to check the following case:
#!/bin/bash
line="abc"
if [[ "${line}" != [a-z] ]]; then
echo INVALID
fi
And I get INVALID as output. But why?
It's no check if $line contains only a characters in the range [a-z] ?
Use the regular expression matching operator =~:
#!/bin/bash
line="abc"
if [[ "${line}" =~ [^a-zA-Z] ]]; then
echo INVALID
fi
Works in any Bourne shell and wastes no pipes/forks:
case $var in
("") echo "empty";;
(*[!a-z]*) echo "contains a non-alphabetic";;
(*) echo "just alphabetics";;
esac
Use [!a-zA-Z] if you want to allow upper case as well.
Could you please try following and let me know if this helps you.
line="abc"
if echo "$line" | grep -i -q '^[a-z]*$'
then
echo "MATCHED."
else
echo "NOT-MATCHED."
fi
Pattern matches are anchored to the beginning and end of the string, so your code checks if $line is not a single lowercase character. You want to match an arbitrary sequence of lowercase characters, which you can do using extended patterns:
if [[ $line != #([a-z]) ]]; then
or using the regular-expression operator:
if ! [[ $line =~ ^[a-z]+$ ]]; then # there is no negative regex operator like Perl's !~
Why? Because != means "not equal", thats why. You tell bash to compare abc with [a-z]. They are not equal.
Try echo $line | grep -i -q -x '[a-z]*'.
The flag -i makes grep case insensitive.
The flag -x means match the whole line.
The flag -q means print nothing to stdout, just return 1 or 0.

How to if "x" match any of an array

I want to know how to write in bash an if statement with select command that echo a prompt error in case non correct option is given.
"The $strings variable is a column of strings."
menu() {
strings=$(echo "$(awk -F'[][]' '{ print $(2) }' file | awk NF)")
select string in $strings
do
if [[ "$string" MATCH ANY "${strings[#]}" ]]
then
echo "you've selected $account"
break
else
echo "Please, select one of the given options." && sleep 2
clear && menu
fi
done
}
I tried with:
if [[ "$string" =~ "${strings[#]} ]]
But does not work.
Can someone help me?
Thank you in advance.
I found the answer. It was as easy as not trying to compare with anything else, like this:
menu() {
strings=$(echo "$(awk -F'[][]' '{ print $(2) }' file | awk NF)")
select string in $strings
do
if [[ "$string" ]]
then
echo "you've selected $account"
break
else
echo "Please, select one of the given options." && sleep 2
clear && menu
fi
done
}
In your code you are missing the closing quote on your if.
it should be:
if [[ "$string" =~ "${strings[#]}" ]]

Bash while loop if statment

Can anyone see whats wrong here? If I put X|9 in lan.db (or any db in this directory) and run the following code, the IF statement does not work. It's weird! if you echo $LINE, it is indeed pulling X|9 out of lan.db (or any db in this directory) and setting it equal to LINE, but it wont do the comparison.
DBREGEX="^[0-9]|[0-9]$"
shopt -s nullglob
DBARRAY=(databases/*)
i=0
for i in "${!DBARRAY[#]}"; do
cat ${DBARRAY[$i]} | grep -v \# | while read LINE; do
echo "$LINE" (Whats weird is that LINE DOES contain X|9)
if [[ !( $LINE =~ $DBREGEX ) ]]; then echo "FAIL"; fi
done
done
If however I just manually sent LINE="X|9" the same code (minus the while) works fine. ie LINE=X|9 fails, but LINE=9|9 succeeds.
DBREGEX="^[0-9]|[0-9]$"
Comment shopt -s nullglob
Comment DBARRAY=(databases/*)
Comment i=0
Comment for i in "${!DBARRAY[#]}"; do
Comment cat ${DBARRAY[$i]} | grep -v \# | while read LINE; do
LINE="X|9"
if [[ !( $LINE =~ $DBREGEX ) ]]; then echo "FAIL"; fi
Comment done
Comment done
* UPDATE *
UGH I GIVE UP
Now not even this is working...
DBREGEX="^[0-9]|[0-9]$"
LINE="X|9"
if [[ ! $LINE =~ $DBREGEX ]]; then echo "FAIL"; fi
* UPDATE *
Ok, so it looks like I have to escape |
DBREGEX="^[0-9]\|[0-9]$"
LINE="9|9"
echo "$LINE"
if [[ ! $LINE =~ $DBREGEX ]]; then echo "FAIL"; fi
This seems to work ok again
| has a special meaning in a regular expression. ^[0-9]|[0-9]$ means "starts with a digit, or ends with a digit". If you want to match a literal vertical bar, backslash it:
DBREGEX='^[0-9]\|[0-9]$'
for LINE in 'X|9' '9|9' ; do
echo "$LINE"
if [[ ! $LINE =~ $DBREGEX ]] ; then echo "FAIL" ; fi
done
You don't need round brackets in regex evaluation. You script is also creating a sub shell and making a useless use of cat which can be avoided.
Try this script instead:
while read LINE; do
echo "$LINE"
[[ "$LINE" =~ $DBREGEX ]] && echo "PASS" || echo "FAIL"
done < <(grep -v '#' databases/lan.db)

Resources