Check if a string matches a certain pattern in tcsh - tcsh

I try to match a user-typed string with a specific pattern, to be exact i want to check if the string starts with an upper case letter and then continues with any upper, lower-case letter or number. I want to do this in tcsh, I know bash is better to use, but I have to use tcsh.
So basically i want the following in tcsh:
if [[ $name =~ ^[A-Z][A-Za-z0-9]*$ ]]
Here is my code so far:
#!/bin/tcsh
set name
while ( $name == "" )
echo 'Give an account name!'
set name = $<
if ( $name =~ '^[A-Z][A-Za-z0-9*$]' ) then
echo 'Name accepted!'
else
echo 'Not correct format!'
set name = ""
endif
end
I'm continously ending up in the "else" part.
Thank you very much for the help!

When using the =~ comparison operator, the right hand side must be a pattern that may include asterisk or question mark wildcards (as in file matching) but not RegEx.
This is a workaround I came up with...
#!/bin/tcsh
set name
while ( $name == "" )
echo 'Give an account name!'
set name = $<
set cond = `expr $name : '^[A-Z][A-Za-z0-9]*$'`
set n = `echo $name | wc -c`
# n--
if ( $cond == $n ) then
echo 'Name accepted!'
else
echo 'Not correct format!'
set name = ""
endif
end
Note that the regular expression also needed fixing.

Related

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.

Bash: Variable string contains variable string with special characters (e.g. "=", "'")

I want to do a basic
if [[ "$string" =~ "$substring" ]]; then
echo "True"
else echo "False"
fi
However, the substring I am looking for looks like "THING = '$VAR '" and that just doesn't fly. Yes, $VAR is a variable in my code. Help?
Here's an example of what I am looking at (the $string is extracted from a file):
# $string = "THIS = stuff / more random letters taking up space MORE STUFF = '42 ' here's some numbers asdgh asdgjhd a;lkdjg asdlkja dddd 'a::' THING = 'R ' more specs = THIS and THAT END"
for VAR in V R I B WA NA HA1 HA4 HA5; do
substring="SUBSET = '$VAR '"
if [[ "$string" =~ "$substring" ]]; then
echo "True"
else echo "False"
fi
done
I apologize if this is not more illuminating. Perhaps having the variable in so many quotations is not actually bringing up the variable, but when I print out $substring, it says THING = 'V '.

KShell regular expression comparision not working

I have the below if condition in my shell script which uses regular expressions. Basically I wanted to find out if $main contains string $pattrn.
main="$line1"
pattrn="$line"
if [[ $main = #($pattrn) ]];
then
echo $line>>/lawson/prod/work/errval
fi
Even If I have few matching values but this If condition in not returning anything.
Thanks in Advance
I think I have a solution, though there might be a more elegant solution with sed or awk.
#!/bin/ksh
variable1="ABCDEF"
variable2="CDE"
printf "variable1( %s )\n" $variable1
if( echo $variable1 | grep -q $variable2 ); then
printf "variable2 appears in variable1\n"
fi
variable3="GHI"
if( echo $variable1 | grep -q $variable3 ); then
printf "variable3 appears in variable1\n"
else
printf "variable3 does not appears in variable1\n"
fi
Output
variable1( ABCDEF )
variable2 appears in variable1
variable3 does not appears in variable1
Explanation: You are checking the return value of the command in parenthesis to ensure that the result is equal to 0. With the grep after the pipe, you are checking if grep found the string within the output string from the echo.
Hope that helps.
I wanted to find out if $main contains string $pattrn.
if [[ $main = #($pattrn) ]]
This tests for an exactly equal match. To test if $main contains $pattrn, we have to specify that there may be text before and after $pattrn:
if [[ $main = #(*$pattrn*) ]]
By the way, since we have just one pattern, we can do without #(…) as well:
if [[ $main = *$pattrn* ]]

tcsh script if statement

I need to loop through a bunch of different scenarios (variable scen), but can't figure out how to use the if statements in the tcsh shell script. Getting the error "if: Expression Syntax" Can someone please tell me what I have wrong? Simplified code follows! Thanks!
#!/bin/tcsh -f
#
set val = 0
foreach scen ( a b )
echo $scen
if ($scen==a) then
echo $scen
else
echo $val
endif
end
Solution to your problem
Apparently you need spaces around the equality comparison ==. This
works:
#!/bin/tcsh -f
#
set val = 0
foreach scen ( a b )
echo $scen
if ($scen == a) then
echo $scen
else
echo $val
endif
end
producing:
a
a
b
0
Unsolicited advice
Also, unless you have to be using tcsh here, I suggest using a better
shell like bash or zsh. Here are some arguments against csh and tcsh:
http://www.shlomifish.org/open-source/anti/csh/
http://www.faqs.org/faqs/unix-faq/shell/csh-whynot/
For comparison, here's your code in bash (and zsh):
#!/bin/bash
# No space around equal sign in assignment!
val=0
for scen in a b; do
echo $scen
if [[ $scen == a ]]; then
echo $scen
else
echo $val
fi
done
There's no important difference here, but see the above articles for
examples where csh would be a bad choice.

How can I check if a string is in an array without iterating over the elements?

Is there a way of checking if a string exists in an array of strings - without iterating through the array?
For example, given the script below, how I can correctly implement it to test if the value stored in variable $test exists in $array?
array=('hello' 'world' 'my' 'name' 'is' 'perseus')
#pseudo code
$test='henry'
if [$array[$test]]
then
do something
else
something else
fi
Note
I am using bash 4.1.5
With bash 4, the closest thing you can do is use associative arrays.
declare -A map
for name in hello world my name is perseus; do
map["$name"]=1
done
...which does the exact same thing as:
declare -A map=( [hello]=1 [my]=1 [name]=1 [is]=1 [perseus]=1 )
...followed by:
tgt=henry
if [[ ${map["$tgt"]} ]] ; then
: found
fi
There will always technically be iteration, but it can be relegated to the shell's underlying array code. Shell expansions offer an abstraction that hide the implementation details, and avoid the necessity for an explicit loop within the shell script.
Handling word boundaries for this use case is easier with fgrep, which has a built-in facility for handling whole-word fixed strings. The regular expression match is harder to get right, but the example below works with the provided corpus.
External Grep Process
array=('hello' 'world' 'my' 'name' 'is' 'perseus')
word="world"
if echo "${array[#]}" | fgrep --word-regexp "$word"; then
: # do something
fi
Bash Regular Expression Test
array=('hello' 'world' 'my' 'name' 'is' 'perseus')
word="world"
if [[ "${array[*]}" =~ (^|[^[:alpha:]])$word([^[:alpha:]]|$) ]]; then
: # do something
fi
You can use an associative array since you're using Bash 4.
declare -A array=([hello]= [world]= [my]= [name]= [is]= [perseus]=)
test='henry'
if [[ ${array[$test]-X} == ${array[$test]} ]]
then
do something
else
something else
fi
The parameter expansion substitutes an "X" if the array element is unset (but doesn't if it's null). By doing that and checking to see if the result is different from the original value, we can tell if the key exists regardless of its value.
array=('hello' 'world' 'my' 'name' 'is' 'perseus')
regex="^($(IFS=\|; echo "${array[*]}"))$"
test='henry'
[[ $test =~ $regex ]] && echo "found" || echo "not found"
Reading your post I take it that you don't just want to know if a string exists in an array (as the title would suggest) but to know if that string actually correspond to an element of that array. If this is the case please read on.
I found a way that seems to work fine .
Useful if you're stack with bash 3.2 like I am (but also tested and working in bash 4.2):
array=('hello' 'world' 'my' 'name' 'is' 'perseus')
IFS=: # We set IFS to a character we are confident our
# elements won't contain (colon in this case)
test=:henry: # We wrap the pattern in the same character
# Then we test it:
# Note the array in the test is double quoted, * is used (# is not good here) AND
# it's wrapped in the boundary character I set IFS to earlier:
[[ ":${array[*]}:" =~ $test ]] && echo "found! :)" || echo "not found :("
not found :( # Great! this is the expected result
test=:perseus: # We do the same for an element that exists
[[ ":${array[*]}:" =~ $test ]] && echo "found! :)" || echo "not found :("
found! :) # Great! this is the expected result
array[5]="perseus smith" # For another test we change the element to an
# element with spaces, containing the original pattern.
test=:perseus:
[[ ":${array[*]}:" =~ $test ]] && echo "found!" || echo "not found :("
not found :( # Great! this is the expected result
unset IFS # Remember to unset IFS to revert it to its default value
Let me explain this:
This workaround is based on the principle that "${array[*]}" (note the double quotes and the asterisk) expands to the list of elements of array separated by the first character of IFS.
Therefore we have to set IFS to whatever we want to use as boundary (a colon in my case):
IFS=:
Then we wrap the element we are looking for in the same character:
test=:henry:
And finally we look for it in the array. Take note of the rules I followed to do the test (they are all mandatory): the array is double quoted, * is used (# is not good) AND it's wrapped in the boundary character I set IFS to earlier:
[[ ":${array[*]}:" =~ $test ]] && echo found || echo "not found :("
not found :(
If we look for an element that exists:
test=:perseus:
[[ ":${array[*]}:" =~ $test ]] && echo "found! :)" || echo "not found :("
found! :)
For another test we can change the last element 'perseus' for 'perseus smith' (element with spaces), just to check if it's a match (which shouldn't be):
array[5]="perseus smith"
test=:perseus:
[[ ":${array[*]}:" =~ $test ]] && echo "found!" || echo "not found :("
not found :(
Great!, this is the expected result since "perseus" by itself is not an element anymore.
Important!: Remember to unset IFS to revert it to its default value (unset) once you're done with the tests:
unset IFS
So so far this method seems to work, you just have to be careful and choose a character for IFS that you are sure your elements won't contain.
Hope it helps anyone!
Regards,
Fred
In most cases, the following would work. Certainly it has restrictions and limitations, but easy to read and understand.
if [ "$(echo " ${array[#]} " | grep " $test ")" == "" ]; then
echo notFound
else
echo found
fi
Instead of iterating over the array elements it is possible to use parameter expansion to delete the specified string as an array item (for further information and examples see Messing with arrays in bash and Modify every element of a Bash array without looping).
(
set -f
export IFS=""
test='henry'
test='perseus'
array1=('hello' 'world' 'my' 'name' 'is' 'perseus')
#array1=('hello' 'world' 'my' 'name' 'is' 'perseusXXX' 'XXXperseus')
# removes empty string as array item due to IFS=""
array2=( ${array1[#]/#${test}/} )
n1=${#array1[#]}
n2=${#array2[#]}
echo "number of array1 items: ${n1}"
echo "number of array2 items: ${n2}"
echo "indices of array1: ${!array1[*]}"
echo "indices of array2: ${!array2[*]}"
echo 'array2:'
for ((i=0; i < ${#array2[#]}; i++)); do
echo "${i}: '${array2[${i}]}'"
done
if [[ $n1 -ne $n2 ]]; then
echo "${test} is in array at least once! "
else
echo "${test} is NOT in array! "
fi
)
q=( 1 2 3 )
[ "${q[*]/1/}" = "${q[*]}" ] && echo not in array || echo in array
#in array
[ "${q[*]/7/}" = "${q[*]}" ] && echo not in array || echo in array
#not in array
#!/bin/bash
test="name"
array=('hello' 'world' 'my' 'yourname' 'name' 'is' 'perseus')
nelem=${#array[#]}
[[ "${array[0]} " =~ "$test " ]] ||
[[ "${array[#]:1:$((nelem-1))}" =~ " $test " ]] ||
[[ " ${array[$((nelem-1))]}" =~ " $test" ]] &&
echo "found $test" || echo "$test not found"
Just treat the expanded array as a string and check for a substring, but to isolate the first and last element to ensure they are not matched as part of a lesser-included substring, they must be tested separately.
if ! grep -q "$item" <<< "$itemlist" ; then .....
Should work fine.
for simple use cases I use something like this
array=( 'hello' 'world' 'I' 'am' 'Joe' )
word=$1
[[ " ${array[*]} " =~ " $word " ]] && echo "$word is in array!"
Note the spaces around ". This works as long as there are no spaces in the array values and the input doesn't match more values at once, like word='hello world'. If there are, you'd have to play with $IFS on top of that.

Resources