I have been working on this function in shell scripting to take up a password from a user and check if it satisfies the criteria of being an effective password but it always says password not strong enough. The password I'm trying to use is LK#12abc.
Here is my code:
function paq()
{
read -p "PASSWORD :-" password
pasq="^(?=.*[A-Z].*[A-Z])(?=.*[!##$&*])(?=.*[0-9].*[0-9])(?=.*[a-z].*[a-z].*[a-z]).{8}$"
if [[ $password =~ $pasq ]]; then
echo "Valid password"
echo "The password is:- $password" >> user1.txt
echo "$password" >>password.txt
else
echo "password not strong enough"
fi
}
paq
It appears your password has to have:
3 lower case letters
2 upper case letters
2 digits
1 punctuation character
exactly 8 characters long
So, with bash glob patterns:
if [[ $password == *[a-z]*[a-z]*[a-z]* ]] &&
[[ $password == *[A-Z]*[A-Z]* ]] &&
[[ $password == *[0-9]*[0-9]* ]] &&
[[ $password == *[!##$\&*]* ]] &&
(( ${#password} == 8 ))
then
echo password OK
else
echo password does not satisfy criteria
fi
I'm surprised that I need to escape the & in the 4th test
As it says in man bash:
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 expression and matched
accordingly (as in regex(3)).
The first problem is that POSIX regex does not allow for lookahead
and lookbehing assertions.
The second problem is that your test password is not correct - you
miss 8 random characters at the end.
The third problem is that you didn't specify the
shebang and didn't quote variables correctly.
All in all, your script should look like that:
#!/usr/bin/env bash
paq() {
read -r -p "PASSWORD :- " password
pasq="^(.*[A-Z].*[A-Z])(.*[!##$&*])(.*[0-9].*[0-9])(.*[a-z].*[a-z].*[a-z]).{8}$"
if [[ "$password" =~ $pasq ]]; then
echo "Valid password"
echo "The password is:- $password" >> user1.txt
echo "$password" >>password.txt
else
echo "password not strong enough"
fi
}
paq
Run it:
$ ./paq.sh
PASSWORD :- AB!11abc12345678
Valid password
You can take your original regex and feed it to grep -P (only if your system's grep supports Perl regular expressions). Then use the script:
while ! echo "$password" | grep -P '^(?=.*[A-Z].*[A-Z])(?=.*[!##$&*])(?=.*[0-9].*[0-9])(?=.*[a-z].*[a-z].*[a-z]).{8}$'
do
read -r -p "PASSWORD: " password
echo -e "Password not strong enough, it needs\n\
3 lower case letters\n\
2 upper case letters\n\
2 digits\n\
1 punctuation character\n\
exactly 8 characters long\n"
done
echo "Valid password"
echo "The password is:- $password" | tee user1.txt
Using tr to translate each wanted character classes to a digit, sorting that digit string and count matching with grep (using #glennjackman defined character classes except the exact length):
echo LK#12abc |
tr '[:digit:]' 1 | # do this first
tr '[:upper:]' 2 |
tr '[:lower:]' 3 |
tr '[:punct:]' 4 |
tr -c '[:digit:]' 5 | # all else to 5
grep -o . | # transpose
sort | # sort
tr -d '\n' | # transpose
if
grep -q "1\{2,\}2\{2,\}3\{3,\}4\{1,\}5" # 5 is the trailing newline
then
echo yes
else
echo no
fi # outputs
yes
( I was trying to tr '[:digit:][:upper:][:lower:]' '123' but apparently that won't work so each class is now processed on its own line. Maybe you could loose the trs, just sort the password string and count match classes with grep)
Related
I am creating a function that will accept an input and determine if the value is a certain type of hash encoding (md5, sha1, sha256, and sha512). I have asked a few classmates and logically it makes sense, but clearly something is wrong.
#!/usr/bin/bash
function identify-hash() {
encryptinput=$(echo $1 | grep -E -i '^[a-z0-9=]+${32}')
if [[ -n $encryptinput ]]; then
echo "The $1 is a valid md5sum string"
exit
else
encryptinput=$(echo $1 | grep -E -i '^[a-z0-9=]+${40}')
if [[ -n $encryptinput ]]; then
echo "The $1 is a valid sha1sum string"
exit
else
encryptinput=$(echo $1 | grep -E -i '^[a-z0-9=]+${64}')
if [[ -n $encryptinput ]]; then
echo "The $1 is a valid sha256sum string"
exit
else
encryptinput=$(echo $1 | grep -E -i '^[a-z0-9=]+${128}')
if [[ -n $encryptinput ]]; then
echo "The $1 is a valid sha512sum string"
exit
else
echo "Unable to determine the hash function used to generate the input"
fi
fi
fi
fi
}
identify-hash $1
I know that hashes have a specific number of characters for them, but I don't know exactly why it's not working. Removing the {32} out of line 4 allows it to answer as a md5sum, but than it assumes everything is md5sum.
Suggestions?
Fixed your script. I advise you would have spotted most of the issues if you had used ShellCheck:
#!/usr/bin/env bash
identify_hash() {
# local variables
local -- encrypt_input
local -- sumname
# Regex capture the hexadecimal digits
if [[ "$1" =~ ([[:xdigit:]]+) ]]; then
encrypt_input="${BASH_REMATCH[1]}"
else
encrypt_input=''
fi
# Determine name of sum algorithm based on length of encrypt_input
case "${#encrypt_input}" in
32) sumname=md5sum ;;
40) sumname=sha1sum ;;
64) sumname=sha256sum ;;
128) sumname=sha512sum ;;
*) sumname=;;
esac
# If sum algorithm name found (sumname is not empty)
if [ -n "$sumname" ]; then
printf 'The %s is a valid %s string\n' "$encrypt_input" "$sumname"
else
printf 'Unable to determine the hash function used to generate the input\n' >&2
exit 1
fi
}
identify_hash "$1"
Something shorter, using bash:
checkHash() {
local -ar sumnames=([32]=md5sum [40]=sha1sum [64]=sha256sum [128]=sha512sum)
[[ "$1" =~ [[:xdigit:]]{32,129} ]]
echo "${sumnames[${#BASH_REMATCH}]+String $BASH_REMATCH could be }${sumnames[
${#BASH_REMATCH}]:-No hash tool match this string.}"
}
This will extract [:xdigit:] part out of any complete line:
checkHash 'Filename: 13aba32dbe4db7a7117ed40a25c29fa8 --'
String 13aba32dbe4db7a7117ed40a25c29fa8 could be md5sum
checkHash a32dba32dbe4db7a7117ed40a25c29fa8e4db7a7117ed40a25c29fa8
No hash tool match this string.
checkHash a32dba32dbe4db7a7117ed40a25c29fa8e4db7a7117ed40a25c29fa8da921adb
String a32dba32dbe4db7a7117ed40a25c29fa8e4db7a7117ed40a25c29fa8da921adb could be sha256sum
... then ${var+return this only if $var exist}
... and ${var:-return this if $var is empty}
Further explaining #Gordon Davissons' comment and some basics for anyone who stops by
NB This answer is extremely simplified to apply only to the current question. here's my preferred guide for more regex
Basics of regex
^ - start of a line
$ - end of a line
[...] - list of possible characters
has special sauce
a-z = all lowercase (English) letters; 0-9 = all digits; etc.
also accepts character classes - e.g [:xdigit:] for hexadecimal characters
the expression is now [[:xdigit:]] - i.e [:class:] inside [...]
{...} - number of times the preceding expression should be matched
^[a]{1}$ will match a but not aa
^f[o]{2}d$ will match food but not fod, foood, fooo*d
^[a-z]{4}$ will match
ball ✔️ but not buffalo ❌
cove ✔️ but not cover ❌
basically any line ( because of the ^...$) containing a string of exactly 4 (English) alphabetic characters
{1,5} - at least 1 and at most 5
* - shorthand for {0,} meaning 0 or any number of times
+ - shorthand for {1,} meaning at least 1; but no upper limit
? - shorthand for {1}
So ${32} is looking for 32 "end of line" \n in jargon and what you need is [a-z0-9=]{32} instead
BUT as also pointed out by Andrej Podzimek in the comments you need to match only hexadecimal [0-9a-f] characters which is the same as [:xdigit:]. Either can be used.
PS
more Basics
. (fullstop/period) matches ANY character including spaces and special characters
(...) is to match patterns
[a-z ]*(chicken).*
will match anything from chicken coop to chicken soup and please pass that chicken cookbook, Alex?
[.] means period/fullstop not any character
note the space after z this is to make space (ascii 32 ) a possible character
and . is case-insensituve
PPS if it's for homework/assignment/schoolwork, please specify so in your question :)
Sometimes I need to find a specific serial in a box with many items, so I wrote a simple Bash script that allows me to use a barcode scanner to scan hundreds of barcodes until a match is found, at which point the screen flashes (so I can see it from the corner of my eyes while looking at the box).
The script works great, but it only checks against one specific serial number provided by the user. Here's the code:
#!/bin/bash
INPUT=''
SCAN=''
SN=''
I='0'
clear
printf "Enter serial\n"
read INPUT
SN=`printf "${INPUT}" | tr '[:lower:]' '[:upper:]'`
# Keep comparing scans to needed serial until a match is found
while [[ "${SCAN}" != *"${SN}"* ]];
do
clear
printf "Looking for [ "${SN}" ]\n"
printf "Please scan barcode\n"
read INPUT
SCAN=`printf "${INPUT}" | tr '[:lower:]' '[:upper:]'`
done
# Flash screen when match is found
while [[ "${I}" -lt 3 ]];
do
printf '\e[?5h' && sleep 0.3
printf '\e[?5l' && sleep 0.3
I=$[${I}+1]
done
printf "FOUND\n"
Today I spent hours trying to implement a way to pass multiple possible serial numbers as command line arguments, but I can't seem to get it working. I would like to be able to pass a small, manageable number of possible serials, like this:
$ ./script.sh sn1 sn2 sn3 sn4 sn5
And for the script continue asking for input until I come across the item I am looking for.
I've studied the handling of shell arguments, but I can't seem to "massage" the above while loop to get it to check if the scanned serial exists in the array (created from the command line arguments passed):
#!/bin/bash
snList=( "$#" )
INPUT=''
SCAN=''
SN=''
I='0'
clear
#displaying "things" so I can see what each variable contains (debugging)
printf "$#\n"
printf "$0\n"
printf "$*\n"
printf "$0\n"
printf "$1\n"
printf "$2\n"
printf "$3\n"
printf "snList: $snList\n"
printf "snList[#]: ${snList[#]}\n"
printf "snList[*]: ${snList[*]}\n"
# Keep comparing scans to needed serial until a match is found
while [[ ! " ${snList[*]} " =~ "${SCAN}" ]];
do
clear
printf "Looking for [ "$*" ]\n"
printf "Please scan barcode\n"
read INPUT
SCAN=`printf "${INPUT}" | tr '[:lower:]' '[:upper:]'`
done
I've tried using ${snList[#]} in the loop as well, same result, it behaves like a match was found immediately, without even asking for a scan (indicating that the content of the while loop is not being executed).
Any help will be immensely appreciated, I think I am close, but I can't figure out what I am doing wrong.
Thanks in advance!
Something like this maybe?
#!/usr/bin/env bash
to_compare_input=("$#")
exglob_pattern_input=$(IFS='|'; printf '%s' "#(${to_compare_input[*]})")
until [[ $user_input == $exglob_pattern_input ]]; do
read -r user_input
done
Run the script with the the following arguments.
bash -x ./myscript foo bar baz more
Output
+ to_compare_input=("$#")
++ IFS='|'
++ printf %s '#(foo|bar|baz|more)'
+ exglob_pattern_input='#(foo|bar|baz|more)'
+ [[ '' == #(foo|bar|baz|more) ]]
+ read -r user_input
papa
+ [[ papa == #(foo|bar|baz|more) ]]
+ read -r user_input
mama
+ [[ mama == #(foo|bar|baz|more) ]]
+ read -r user_input
baz
+ [[ baz == #(foo|bar|baz|more) ]]
The first user input is empty since the builtinread has not been executed to ask for the user's input. As shown at the debug message.
+ [[ '' == #(foo|bar|baz|more) ]]
The second (assuming the user has entered papa) is papa
The third (assuming the user has entered mama) is mama
The last is baz which breaks out of off the until loop, because it belongs to the $extglob_pattern_input, which is an extglob feature.
A regex is also an alternative using the =~ operator.
#!/usr/bin/env bash
to_compare_input=("$#")
regex_pattern_input=$(IFS='|'; printf '%s' "^(${to_compare_input[*]})$")
until [[ $user_input =~ $regex_pattern_input ]]; do
read -r user_input
done
Run the script same as before.
Using two loops which was suggested in the comments section.
#!/usr/bin/env bash
to_compare_input=("$#")
inarray() {
local n=$1 h
shift
for h; do
[[ $n == "$h" ]] && return
done
return 1
}
until inarray "$user_input" "${to_compare_input[#]}"; do
read -r user_input
done
As for the tr if your version of bash supports the ^^ and ,, for uppercase and lowercase parameter expansion. use ${user_input^^}
until [[ ${user_input^^} == $exglob_pattern_input ]]; do
until [[ ${user_input^^} =~ $regex_pattern_input ]]; do
until inarray "${user_input^^}" "${to_compare_input[#]}"; do
Assuming no spaces in the bar code texts. You can do something like this
while read -r INPUT
do
#Append spaces to prevent substring matching
if [[ $(echo " $# " | grep -i " ${INPUT} " | wc -l) -eq 1 ]]
then
break
fi
done
I am writing a password check script and I would like to be able to check if password includes a string of 3 or more of the same characters (aaa, babbb, asj111) all would be a "bad password". I have tried
grep -E '(.)\1{2,}' $password
but I want this in an if statement to then do something if I find more than 3 characters. All help is appreciated!
Use the return code of grep:
if grep -q '\(.\)\1\1' <<< "$password"; then
echo "Bad password!"
# Do something, like exit 1
fi
The -q option is to keep grep quiet. I didn't use the -E (extended) option, use it if you want.
Put your pattern in a variable. It matches any character, then the same character twice again (so three times in total).
pattern='(.)\1{2}'
Then test it with the bash regex comparison operator =~
[[ $password =~ $pattern ]] && echo "bad password"
How to convert the alternative character of a string passed to script, if it is lower then it should be converted to upper and if it is upper then to lower??
read -p " Enter string" str
for i in `seq 0 ${#str}`
do
#echo $i
rem=$(($i % 2 ))
if [ $rem -eq 0 ]
then
echo ${str:$i:1}
else
fr=${str:$i:1}
if [[ "$fr" =~ [A-Z] ]]
then
echo ${str:$i:1} | tr '[:upper:]' '[:lower:]'
elif [[ "$fr" =~ [a-z] ]]
then
echo ${str:$i:1} | tr '[:lower:]' '[:upper:]'
else
echo ""
fi
fi
done
Your question is a bit challenging given that it is tagged shell and not as a question pertaining to an advanced shell like bash or zsh. In POSIX shell, you have no string indexes, no C-style for loop, and no [[ .. ]] operator to use character class pattern matching.
However, with a bit of awkward creativity, the old expr and POSIX string and arithmetic operations, and limiting your character strings to ASCII characters, you can iterate over a string changing uppercase to lowercase and lowercase and uppercase while leaving all other characters unchanged.
I wouldn't recommend the approach if you have an advanced shell available, but if you are limited to POSIX shell, as your question is tagged, it will work, but don't expect it to be super-fast...
#!/bin/sh
a=${1:-"This Is My 10TH String"} ## input and output strings
b=
i=1 ## counter and string length
len=$(expr length "$a")
asciiA=$(printf "%d" "'A") ## ASCII values for A,Z,a,z
asciiZ=$(printf "%d" "'Z")
asciia=$(printf "%d" "'a")
asciiz=$(printf "%d" "'z")
echo "input : $a" ## output original string
while [ "$i" -le "$len" ]; do ## loop over each character
c=$(expr substr "$a" "$i" "1") ## extract char from string
asciic=$(printf "%d" "'$c") ## convert to ASCII value
## check if asciic is [A-Za-z]
if [ "$asciiA" -le "$asciic" -a "$asciic" -le "$asciiZ" ] ||
[ "$asciia" -le "$asciic" -a "$asciic" -le "$asciiz" ]
then ## toggle the sign bit (bit-6)
b="${b}$(printf "\x$(printf "%x" $((asciic ^ 1 << 5)))\n")"
else
b="$b$c" ## otherwise copy as is
fi
i=$(expr $i + 1)
done
echo "output: $b" ## output resluting string
The case change is affected by relying on a simple bit-toggle of the case-bit (bit-6) in the ASCII value of each upper or lower case character to change it from lower to upper or vice-versa. (and note, you can exchange the printf and bit-shift for tr of asciic as an alternative)
Example Use/Output
$ sh togglecase.sh
input : This Is My 10TH String
output: tHIS iS mY 10th sTRING
When you want to swab every second characters case, try this:
read -p " Enter string " str
for i in `seq 0 ${#str}`; do
rem=$(($i % 2 ))
if [ $rem -eq 0 ]
then
printf "%s" "${str:$i:1}"
else
fr=${str:$i:1}
printf "%s" "$(tr '[:upper:][:lower:]' '[:lower:][:upper:]' <<< "${str:$i:1}")"
fi
done
echo
EDIT: Second solution
Switch case of str and merge the old and new string.
#!/bin/bash
str="part is lowercase & PART IS UPPERCASE"
str2=$(tr '[:upper:][:lower:]' '[:lower:][:upper:]' <<< "${str}")
str_chopped=$(sed -r 's/(.)./\1\n/g' <<< "${str}");
# Will have 1 additional char for odd length str
# str2_chopped_incorrect=$(sed -r 's/.(.)/\1\n/g' <<< "${str2}");
str2_chopped=$(fold -w2 <<< "${str2}" | sed -nr 's/.(.)/\1/p' );
paste -d '\n' <(echo "${str_chopped}") <(echo "${str2_chopped}") | tr -d '\n'; echo
Hi I'm looking to write a simple script which takes an input letter and outputs it's numerical equivalent :-
I was thinking of listing all letters as variables, then have bash read the input as a variable but from here I'm pretty stuck, any help would be awesome!
#!/bin/bash
echo "enter letter"
read "LET"
a=1
b=2
c=3
d=4
e=5
f=6
g=7
h=8
i=9
j=10
k=11
l=12
m=13
n=14
o=15
p=16
q=17
r=18
s=19
t=20
u=21
v=22
w=23
x=24
y=25
z=26
LET=${a..z}
if
$LET = [ ${a..z} ];
then
echo $NUM
sleep 5
echo "success!"
sleep 1
exit
else
echo "FAIL :("
exit
fi
Try this:
echo "Input letter"
read letter
result=$(($(printf "%d\n" \'$letter) - 65))
echo $result
0
ASCII equivalent of 'A' is 65 so all you've got to do to is to take away 65 (or 64, if you want to start with 1, not 0) from the letter you want to check. For lowercase the offset will be 97.
A funny one, abusing Bash's radix system:
read -n1 -p "Type a letter: " letter
if [[ $letter = [[:alpha:]] && $letter = [[:ascii:]] ]]; then
printf "\nCode: %d\n" "$((36#$letter-9))"
else
printf "\nSorry, you didn't enter a valid letter\n"
fi
The interesting part is the $((36#$letter-9)). The 36# part tells Bash to understand the following string as a number in radix 36 which consists of a string containing the digits and letters (case not important, so it'll work with uppercase letters too), with 36#a=10, 36#b=11, …, 36#z=35. So the conversion is just a matter of subtracting 9.
The read -n1 only reads one character from standard input. The [[ $letter = [[:alpha:]] && $letter = [[:ascii:]] ]] checks that letter is really an ascii letter. Without the [[:ascii:]] test, we would validate characters like é (depending on locale) and this would mess up with the conversion.
use these two functions to get chr and ord :
chr() {
[ "$1" -lt 256 ] || return 1
printf "\\$(printf '%03o' "$1")"
}
ord() {
LC_CTYPE=C printf '%d' "'$1"
}
echo $(chr 97)
a
USing od and tr
echo "type letter: "
read LET
echo "$LET" | tr -d "\n" | od -An -t uC
OR using -n
echo -n "$LET" | od -An -t uC
If you want it to start at a=1
echo $(( $(echo -n "$LET" | od -An -t uC) - 96 ))
Explanation
Pipes into the tr to remove the newline.
Use od to change to unsigned decimal.
late to the party: use an associative array:
# require bash version 4
declare -A letters
for letter in {a..z}; do
letters[$letter]=$((++i))
done
read -p "enter a single lower case letter: " letter
echo "the value of $letter is ${letters[$letter]:-N/A}"