regular expressions in tcsh shell script - regex

I have a shell script in tcsh to which I pass an argument, the length of which can vary. The possible values of the argument are the letters -c,s,i,q,a. and also a combination of these letters. (e.g: cs,si,ca,iq,qa,csq,acs,csia ..etc). The order of the letters does not matter.
My problem is to check the argument for any character other than these 5 and if any of the valid character appear more than one time (zero time is ok). The script should check both the conditions and throw an error. Is there any regular expression that I can use with if statement for this purpose?

Here is a sample piece of code you can use. The use of an "X" is to force the comparison to be a string.
#!/bin/csh -f
if ( $#argv > 0 ) then
echo arg is $1
if ( X$1 =~ X-* ) then
echo "we have an argument"
if ( "X$1" =~ X-c[aeiou] ) then
echo I found -c followed by vowel
else if ( "X$1" =~ "X-c" ) then
echo I found -c alone
else
echo I found a -c but not a valid combo
endif
else
echo I found an unknown argument: $1
endif
endif

This will be easiest to do with two regex checks, one for validity of all letters and another to look for duplicate letters.
Take a look at this code:
#!/bin/tcsh
echo $1 | grep -q -e "[^csqai]"
if ( $? != 0 ) then
echo "Valid characters"
else
echo "Invalid characters"
endif
echo $1 | grep -q -e "\([csqai]\).*\1"
if ( $? != 0 ) then
echo "No repeated valid characters"
else
echo "Repeated valid characters"
endif
The first regex looks for all characters which are not valid and the second looks for any repeated characters
I don't know how to do these checks in tcsh, so I did them with grep. The -q flag makes grep silent. $? is 0 if no match is found.

Related

Bash Shell help regular expressions not working on password check

I am trying to make regular expression work with my password checker script but for some reason it is just not accepting it. What I am trying to do is require the following characters, the minimum of:
two upper case letters, two lower case letters, minimum 2 numbers and minimum two special characters. The special characters I am looking towards are just "!#" and that is it.
Here is the script,
#!/bin/bash
read -s -p "Enter Password: " password
password_length=${#password}
if [ $password_length -lt 8 -o $password_length -gt 20 ] ;then
echo -e "Invalid password - should be between 8 and 20 characters in length.";
echo ;
else
# Check for invalid characters
case $password in
*[^a-zA-Z0-9]* )
echo -e "Password contains invalid characters.";
echo ;
;;
* )
echo "Password accepted.";
echo;;
esac
fi
I don't have a good answer. At the moment I'm playing around with using grep as it has a far more powerful regex engine.
Something like this:
#!/usr/bin/env bash
while true; do
echo "Password must contain 2 upper case characters and two numbers"
read -s -p "Enter Password: " password
echo
FAIL=no
# Mininum 8 characters
[[ ${#password} -ge 8 ]] || FAIL=yes
# Must contain 2 upper case letters
echo $password | grep -q "[A-Z].*[A-Z]" || FAIL=yes
# Must contain 2 digits
echo $password | grep -q "[0-9].*[0-9]" || FAIL=yes
# Must contain one non-alphanumeric character (no spaces)
echo $password | grep -q "[^a-zA-Z0-9 ]" || FAIL=yes
[[ ${FAIL} == "no" ]] && break
echo "Password invalid"
done
echo "Password $password is valid"
Anyway, what I would do is to test the possible password against each requirement separately, as it's easy to get lost in regular expressions, and you might miss some edge cases.
BASH regex engine doesn't support lookarounds as in your regex.
This example is for :
Password should be at least 6 characters long with at least one digit and at least one Upper case Alphabet.
You can use following shell glob check to make sure password matches your criteria:
[[ ${#s} -ge 6 && "$s" == [A-Z] && "$s" == [a-z] && "$s" == [0-9] ]]
ref : Regex password validation in Bash shell

How to check if string contains characters in regex pattern in shell?

How do I check if a variable contains characters (regex) other than 0-9a-z and - in pure bash?
I need a conditional check. If the string contains characters other than the accepted characters above simply exit 1.
One way of doing it is using the grep command, like this:
grep -qv "[^0-9a-z-]" <<< $STRING
Then you ask for the grep returned value with the following:
if [ ! $? -eq 0 ]; then
echo "Wrong string"
exit 1
fi
As #mpapis pointed out, you can simplify the above expression it to:
grep -qv "[^0-9a-z-]" <<< $STRING || exit 1
Also you can use the bash =~ operator, like this:
if [[ ! "$STRING" =~ [^0-9a-z-] ]] ; then
echo "Valid";
else
echo "Not valid";
fi
case has support for matching:
case "$string" in
(+(-[[:alnum:]-])) true ;;
(*) exit 1 ;;
esac
the format is not pure regexp, but it works faster then separate process with grep - which is important if you would have multiple checks.
Using Bash's substitution engine to test if $foo contains $bar
bar='[^0-9a-z-]'
if [ -n "$foo" -a -z "${foo/*$bar*}" ] ; then
echo exit 1
fi

Bash RegEx to check floating point numbers from user input

I'm relatively new to bash programming and i am currently creating a simple calculator.
It needs to use floating point numbers and check they are so.
I have a checkNumbers function:
function checkNumber {
regExp=[0-9]
if [ $testNo =~ $regExp ]
then
echo "That is a number!"
let check=1
else
echo "Damn! Not A Number!"
fi
}
where i get the user to input a number like this:
while [ $check -eq 0]
do
echo "Please enter a number
read testNo
echo ""
checkNumber
done
This does not work, i get this error:
./calculator.sh: line 39: [: =~: binary operator expected
line 39 is:
if [ $testNo =~ $regExp ]
I have tried lots of Regular Expressions like:
^*[0-9]*$
and
^*[0-9]\.[0.9]*$
etc etc.
also, i have tied other ways of performing the check:
case $testNo
in
^*[0-9]*$) echo "That is a number!";;
*) echo "Error! Not a number!";;
esac
and
if [ $testNo = ^*[0-9]*$ ]
then
echo "etc etc"
else
echo "oops"
fi
I also need this to work with floating point numbers.
could someone please let me know how i go about this?
This regex ^[-+]?[0-9]+\.?[0-9]*$ will match only digits with an optional .:
$ echo 30 | grep -Eq '^[-+]?[0-9]+\.?[0-9]*$' && echo Match
Match
$ echo 30.10 | grep -Eq '^[-+]?[0-9]+\.?[0-9]*$' && echo Match
Match
$ echo 30. | grep -Eq '^[-+]?[0-9]+\.?[0-9]*$' && echo Match
Match
$ echo +30 | grep -Eq '^[-+]?[0-9]+\.?[0-9]*$' && echo Match
Match
$ echo -30 | grep -Eq '^[-+]?[0-9]+\.?[0-9]*$' && echo Match
Match
I think when you tried ^*[0-9] you wanted ^[0-9]*
Rexeplanation:
^ # Match start of string
[-+]? # Match a leading + or - (optional)
[0-9]+ # Match one or more digit
\.? # Match a literal . (optional, escaped)
[0-9]* # Match zero or more digits
$ # Match the end of the string
Note: this matches numbers followed by a . like 30., not sure if this is acceptable for you.
Edit: Don't quote the regex
testNo=30.00
if [[ $testNo =~ ^[+-]?[0-9]+\.?[0-9]*$ ]]; then
echo Match
fi
>>> Match
To use that type of feature, you need the [[ ... ]] version of the conditional. [ is the "old" test command and doesn't handle regular expressions at all.
#! /bin/bash
function checkNumber {
regExp='^[+-]?([0-9]+\.?|[0-9]*\.[0-9]+)$'
if [[ $testNo =~ $regExp ]]
then
echo "That is a number!"
let check=1
else
echo "Damn! Not A Number!"
fi
}
testNo=1
checkNumber
testNo=-1.2
checkNumber
testNo=+.2
checkNumber
testNo=+0.
checkNumber
testNo=a
checkNumber
testNo=hello2you
checkNumber
$ ./t.sh
That is a number!
That is a number!
That is a number!
That is a number!
Damn! Not A Number!
Damn! Not A Number!
See What is the difference between test, [ and [[ ?.
An explanation on the regex:
^ Anchor at start of string
$ Anchor at end of string
These two make the regex match the whole string passed, partial matches are not allowed.
[+-]
matches either + or -.
[+-]?
makes that part optional, so the above matches exactly +, - or nothing at all.
Then there's an alternation (part1|part2) which will match if part1 or part2 matches.
Part one is:
[0-9]+\.?
which matches one or more (+) digits (but not zero digits/empty set) and an optional .. This handles numbers of the form 123 and 534.. But not just ..
Part two is:
[0-9]*\.[0-9]+
This matches zero or more (*) digits, followed by a ., followed by one or more digits. This matches all other floats like 1.3 or .543 (without exponent notation), but still excludes just ..
#!/bin/bash
#script to validate
while [ true ]; do
clear
echo "Introduce the numeric value: "
read -r var
if [[ $var =~ ^[+-]?([0-9]+)$ ]]
then
echo "You introduced an integer value (without decimals)"
read
else
if [[ $var =~ ^[+-]?([0-9]+\.)$ ]]
then
echo "Incomplete floating value (no values provided at the right of the point)"
read
else
if [[ $var =~ ^[+-]?(\.[0-9]+)$ ]]
then
echo "Incomplete floating value (no values provided at the left of the point)"
read
else
if [[ $var =~ ^[+-]?([0-9]+\.[0-9]+)$ ]]
then
echo "You introduced a correct floating value"
read
else
echo "You introduced something other than a valid numeric value"
read
fi
fi
fi
fi
done
I include an optional dot at the start.
user#debian:~$ testNo=.01
user#debian:~$ if [[ $testNo =~ ^[+-]?[.]?[0-9]+\.?[0-9]*$ ]];
> then
> echo Match;
> fi
Match

regex to match strings not preceded by a bang

In bash, I am trying to match valid attributes that are present in an array. Attributes may be 'disabled' by preceding them with a bang (exclamation mark, !), in which case they must not be matched. I have this:
[[ ${TESTS[#]} =~ [^\!]match ]]
which will return true if the word 'match' is in TESTS and not preceded by a !.
It works, except when the word match is in the first position in the array. The problem is the regexp is saying 'match preceded by something that isn't a !'. When it's the first item it is preceded by nothing and therefore does not match.
How do I modify the above to say 'match not preceded by !' ?
From reading answers to other questions I have tried (?<!!)match but this does not work.
Use this re:
([^\!]|^)match
Example of usage:
$ [[ match =~ (^|[^\!])match ]] && echo matches || echo "doesn't match"
matches
$ [[ xmatch =~ (^|[^\!])match ]] && echo matches || echo "doesn't match"
matches
$ [[ '!match' =~ (^|[^\!])match ]] && echo match || echo "doesn't match"
doesn't match
In general, it would be also correct to use assertions here, but bash uses POSIX regular expressions and they know nothing about assertions. But with grep (GNU grep), or perl, or anything that supports PCRE you can do it:
$ echo match | grep -qP '(?<!!)match' && echo matches || echo "doesn't match"
matches
$ echo xmatch | grep -qP '(?<!!)match' && echo matches || echo "doesn't match"
matches
$ echo '!match' | grep -qP '(?<!!)match' && echo matches || echo "doesn't match"
doesn't match

use regular expression in if-condition in bash

I wonder the general rule to use regular expression in if clause in bash?
Here is an example
$ gg=svm-grid-ch
$ if [[ $gg == *grid* ]] ; then echo $gg; fi
svm-grid-ch
$ if [[ $gg == ^....grid* ]] ; then echo $gg; fi
$ if [[ $gg == ....grid* ]] ; then echo $gg; fi
$ if [[ $gg == s...grid* ]] ; then echo $gg; fi
$
Why the last three fails to match?
Hope you could give as many general rules as possible, not just for this example.
When using a glob pattern, a question mark represents a single character and an asterisk represents a sequence of zero or more characters:
if [[ $gg == ????grid* ]] ; then echo $gg; fi
When using a regular expression, a dot represents a single character and an asterisk represents zero or more of the preceding character. So ".*" represents zero or more of any character, "a*" represents zero or more "a", "[0-9]*" represents zero or more digits. Another useful one (among many) is the plus sign which represents one or more of the preceding character. So "[a-z]+" represents one or more lowercase alpha character (in the C locale - and some others).
if [[ $gg =~ ^....grid.*$ ]] ; then echo $gg; fi
Use
=~
for regular expression check Regular Expressions Tutorial Table of Contents
if [[ $gg =~ ^....grid.* ]]
Adding this solution with grep and basic sh builtins for those interested in a more portable solution (independent of bash version; also works with plain old sh, on non-Linux platforms etc.)
# GLOB matching
gg=svm-grid-ch
case "$gg" in
*grid*) echo $gg ;;
esac
# REGEXP
if echo "$gg" | grep '^....grid*' >/dev/null ; then echo $gg ; fi
if echo "$gg" | grep '....grid*' >/dev/null ; then echo $gg ; fi
if echo "$gg" | grep 's...grid*' >/dev/null ; then echo $gg ; fi
# Extended REGEXP
if echo "$gg" | egrep '(^....grid*|....grid*|s...grid*)' >/dev/null ; then
echo $gg
fi
Some grep incarnations also support the -q (quiet) option as an alternative to redirecting to /dev/null, but the redirect is again the most portable.
#OP,
Is glob pettern not only used for file names?
No, "glob" pattern is not only used for file names. you an use it to compare strings as well. In your examples, you can use case/esac to look for strings patterns.
gg=svm-grid-ch
# looking for the word "grid" in the string $gg
case "$gg" in
*grid* ) echo "found";;
esac
# [[ $gg =~ ^....grid* ]]
case "$gg" in ????grid*) echo "found";; esac
# [[ $gg =~ s...grid* ]]
case "$gg" in s???grid*) echo "found";; esac
In bash, when to use glob pattern and when to use regular expression? Thanks!
Regex are more versatile and "convenient" than "glob patterns", however unless you are doing complex tasks that "globbing/extended globbing" cannot provide easily, then there's no need to use regex.
Regex are not supported for version of bash <3.2 (as dennis mentioned), but you can still use extended globbing (by setting extglob ). for extended globbing, see here and some simple examples here.
Update for OP: Example to find files that start with 2 characters (the dots "." means 1 char) followed by "g" using regex
eg output
$ shopt -s dotglob
$ ls -1 *
abg
degree
..g
$ for file in *; do [[ $file =~ "..g" ]] && echo $file ; done
abg
degree
..g
In the above, the files are matched because their names contain 2 characters followed by "g". (ie ..g).
The equivalent with globbing will be something like this: (look at reference for meaning of ? and * )
$ for file in ??g*; do echo $file; done
abg
degree
..g