how do i verify presence of special characters in a bash password generator - bash

Supposed to be a simple bash script, but turned into a monster. This is the 5th try. You don't even want to see the 30 line monstrosity that was attempt #4.. :)
Here's what I want to do: Script generates a random password, with $1=password length, and $2=amount of special characters present in the output.
Or at least, verify before sending to standard out, that at least 1 special character exists. I would prefer the former, but settle for the latter.
Here's my very simple 5th version of this script. It has no verification, or $2:
#!/bin/bash
cat /dev/urandom | tr -dc [=!=][=#=][=#=][=$=][=%=][=^=][:alnum:] | head -c $1
This works just fine, and it's a sufficiently secure password with Usage:
$ passgen 12
2ZuQacN9M#6!
But it, of course, doesn't always print special characters, and it's become an obsession for me now to be able to allow selection of how many special characters are present in the output. It's not as easy as I thought.
Make sense?
By the way, I don't mind a complete rework of the code, I'd be very interested to see some creative solutions!
(By the way: I've tried to pipe it into egrep/grep in various ways, to no avail, but I have a feeling that is a possible solution...)
Thanks
Kevin

How about this:
HASRANDOM=0
while [ $HASRANDOM -eq 0 ]; do
PASS=`cat /dev/urandom | tr -dc [=!=][=#=][=#=][=$=][=%=][=^=][:alnum:] | head -c $1`
if [[ "$PASS" =~ "[~\!#\#\$%^&\*\(\)\-\+\{\}\\\/=]{$2,}" ]]; then
HASRANDOM=1
fi
done
echo $PASS
Supports specifying characters in the output. You could add characters in the regex though I couldn't seem to get square brackets to work even when escaping them.
You probably would want to add some kind of check to make sure it doesn't loop infinitely (though it never went that far for me but I didn't ask for too many special characters either)

Checking for special characters is easy:
echo "$pass" | grep -q '[^a-zA-Z0-9]'
Like this:
while [ 1 ]; do
pass=`cat /dev/urandom | tr -dc [=!=][=#=][=#=][=$=][=%=][=^=][:alnum:] | head -c $1`
if echo "$pass" | grep -q '[^a-zA-Z0-9]'; then
break;
fi
done
And finally:
normal=$(($1 - $2))
(
for ((i=1; i <= $normal; i++)); do
cat /dev/urandom | tr -dc [:alnum:] | head -c 1
echo
done
for ((i=1; i <= $2; i++)); do
cat /dev/urandom | tr -dc [=!=][=#=][=#=][=$=][=%=][=^=] | head -c 1
echo
done
) | shuf | sed -e :a -e '$!N;s/\n//;ta'

Keep it simple... Solution in awk that return the number of "special characters" in input
BEGIN {
FS=""
split("!##$%^",special,"")
}
{
split($0,array,"")
}
END {
for (i in array) {
for (s in special) {
if (special[s] == array[i])
tot=tot+1
}
}
print tot
}
Example output for a2ZuQacN9M#6! is
2
Similar approach in bash:
#!/bin/bash
MyString=a2ZuQacN9M#6!
special=!##$%^
i=0
while (( i++ < ${#MyString} ))
do
char=$(expr substr "$MyString" $i 1)
n=0
while (( n++ < ${#special} ))
do
s=$(expr substr "$special" $n 1)
if [[ $s == $char ]]
then
echo $s
fi
done
done

You may also use a character class in parameter expansion to delete all special chars in a string and then apply some simple Bash string length math to check if there was a minimum (or exact) number of special chars in the password.
# example: delete all punctuation characters in string
str='a!#%3"'
echo "${str//[[:punct:]]/}"
# ... taking Cfreak's approach we could write ...
(
set -- 12 3
strlen1=$1
strlen2=0
nchars=$2
special_chars='[=!=][=#=][=#=][=$=][=%=][=^=]'
HASRANDOM=0
while [ $HASRANDOM -eq 0 ]; do
PASS=`cat /dev/urandom | LC_ALL=C tr -dc "${special_chars}[:alnum:]" | head -c $1`
PASS2="${PASS//[${special_chars}]/}"
strlen2=${#PASS2}
#if [[ $((strlen1 - strlen2)) -eq $nchars ]]; then # set exact number of special chars
if [[ $((strlen1 - strlen2)) -ge $nchars ]]; then # set minimum number of special chars
echo "$PASS"
HASRANDOM=1
fi
done
)

You can count the number of special chars using something like:
number of characters - number of non special characters
Try this:
$ # define a string
$ string='abc!d$'
$ # extract non special chars to letters
$ letters=$(echo $string | tr -dc [:alnum:] )
$ # substract the number on non special chars from total
$ echo $(( ${#string} - ${#letters} ))
2
The last part $(( ... )) evaluate a mathematical expression.

Related

Counting all the 5 from a specific range in Bash

I want to count how many times the digit "5" appears from the range 1 to 4321. For example, the number 5 appears 1 or the number 555, 5 would appear 3 times etc.
Here is my code so far, however, the results are 0, and they are supposed to be 1262.
#!/bin/bash
typeset -i count5=0
for n in {1..4321}; do
echo ${n}
done | \
while read -n1 digit ; do
if [ `echo "${digit}" | grep 5` ] ; then
count5=count5+1
fi
done | echo "${count5}"
P.s. I am looking to fix my code so it can print the right output. I do not want a completely different solution or a shortcut.
What about something like this
seq 4321 | tr -Cd 5 | wc -c
1262
Creates the sequence, delete everything but 5's and count the chars
The main problem here is http://mywiki.wooledge.org/BashFAQ/024. With minimal changes, your code could be refactored to
#!/bin/bash
typeset -i count5=0
for n in {1..4321}; do
echo $n # braces around ${n} provide no benefit
done | # no backslash required here; fix weird indentation
while read -n1 digit ; do
# prefer modern command substitution syntax over backticks
if [ $(echo "${digit}" | grep 5) ] ; then
count5=count5+1
fi
echo "${count5}" # variable will not persist outside subprocess
done | head -n 1 # so instead just print the last one after the loop
With some common antipatterns removed, this reduces to
#!/bin/bash
printf '%s\n' {1..4321} |
grep 5 |
wc -l
A more efficient and elegant way to do the same is simply
printf '%s\n' {1..4321} | grep -c 5
One primary issue:
each time results are sent to a pipe said pipe starts a new subshell; in bash any variables set in the subshell are 'lost' when the subshell exits; net result is even if you're correctly incrementing count5 within a subshell you'll still end up with 0 (the starting value) when you exit from the subshell
Making minimal changes to OP's current code:
while read -n1 digit ; do
if [ `echo "${digit}" | grep 5` ]; then
count5=count5+1
fi
done < <(for n in {1..4321}; do echo ${n}; done)
echo "${count5}"
NOTE: there are a couple performance related issues with this method of coding but since OP has explicitly asked to a) 'fix' the current code and b) not provide any shortcuts ... we'll leave the performance fixes for another day ...
A simpler way to get the number for a certain n would be
nx=${n//[^5]/} # Remove all non-5 characters
count5=${#nx} # Calculate the length of what is left
A simpler method in pure bash could be:
printf -v seq '%s' {1..4321} # print the sequence into the variable seq
fives=${seq//[!5]} # delete all characters but 5s
count5=${#fives} # length of the string is the count of 5s
echo $count5 # print it
Or, using standard utilities tr and wc
printf '%s' {1..4321} | tr -dc 5 | wc -c
Or using awk:
awk 'BEGIN { for(i=1;i<=4321;i++) {$0=i; x=x+gsub("5",""); } print x} '

Q: find longest string using for loop (Bash)

Im learning bash, and I have an assignment where I need to iterate through a list of strings in bash using a for loop, and return the longest string.
This is what I've written:
max=-1
word=""
list=`cat random-text.txt | tr -s [:space:] " " | sed -r 's/([.* ])/\1\n/g' | grep -E "^a.*" | sed -r 's/(.*)[[:space:]]/\1/' | tr -s [:space:] " "`
for i in $list; do
int=`$i | wc -c`
if [ $int > $max ]; then
max=$int
word=$i
fi
done
echo The longest word in $infile that starts with $char is $i
that's probably a bit messy, but I'm having trouble using the for loop (I need the echo function at the end to return the longest string I have found iterating through the array.
** that's a part of a longer script I've written, I
Thanks in advance, much appreciated!
for some reason, while I run this script I get an error which says: "Command 'an' not found
That's because you erroneously used $i | to feed the content of variable i to wc; correct is <<<$i instead (with Bash). But better use just int=${#i}.
Then in $int > $max the > is interpreted as an output redirection; the correct arithmetic comparison operator is -gt.
Finally you don't echo the longest word found, but rather the last processed one; change $i to $word there.

How to cut variables which are beteween quotes from a string

I had problem with cut variables from string in " quotes. I have some scripts to write for my sys classes, I had a problem with a script in which I had to read input from the user in the form of (a="var1", b="var2")
I tried the code below
#!/bin/bash
read input
a=$($input | cut -d '"' -f3)
echo $a
it returns me a error "not found a command" on line 3 I tried to double brackets like
a=$(($input | cut -d '"' -f3)
but it's still wrong.
In a comment the OP gave a working answer (should post it as an answer):
#!/bin/bash
read input
a=$(echo $input | cut -d '"' -f2)
b=$(echo $input | cut -d '"' -f4)
echo sum: $(( a + b))
echo difference: $(( a - b))
This will work for user input that is exactly like a="8", b="5".
Never trust input.
You might want to add the check
if [[ ${input} =~ ^[a-z]+=\"[0-9]+\",\ [a-z]+=\"[0-9]+\"$ ]]; then
echo "Use your code"
else
echo "Incorrect input"
fi
And when you add a check, you might want to execute the input (after replacing the comma with a semicolon).
input='testa="8", testb="5"'
if [[ ${input} =~ ^[a-z]+=\"[0-9]+\",\ [a-z]+=\"[0-9]+\"$ ]];
then
eval $(tr "," ";" <<< ${input})
set | grep -E "^test[ab]="
else
echo no
fi
EDIT:
#PesaThe commented correctly about BASH_REMATCH:
When you use bash and a test on the input you can use
if [[ ${input} =~ ^[a-z]+=\"([0-9]+)\",\ [a-z]+=\"([0-9])+\"$ ]];
then
a="${BASH_REMATCH[1]}"
b="${BASH_REMATCH[2]}"
fi
To extract the digit 1 from a string "var1" you would use a Bash substring replacement most likely:
$ s="var1"
$ echo "${s//[^0-9]/}"
1
Or,
$ a="${s//[^0-9]/}"
$ echo "$a"
1
This works by replacing any non digits in a string with nothing. Which works in your example with a single number field in the string but may not be what you need if you have multiple number fields:
$ s2="1 and a 2 and 3"
$ echo "${s2//[^0-9]/}"
123
In this case, you would use sed or grep awk or a Bash regex to capture the individual number fields and keep them distinct:
$ echo "$s2" | grep -o -E '[[:digit:]]+'
1
2
3

Need help for string manipulation in a bash script

I'm not use to the syntax of bash script. I'm trying to read a file. For each line I want to keep only the part of the string before the delimiter '/' and put it back into a new file if the word respect a perticular length. I've download a dictionary, but the format does not meet my expectation. Since there is 84000 words, I don't really want to manualy remove what after the '/' for each word. I though it would be an easy thing and I follow couple of idea in other similar question on this site, but it seem that I'm missing something somewhere because it still doesn't work. I can't get the length right. The file Test_Input contains one word per line. Here's the code:
#!/usr/bin/bash
filename="Test_Input.txt"
while read -r line
do
sub= echo $line | cut -d '/' -f1
length= echo ${#sub}
if $length >= 4 && $length <= 10;
then echo $sub >> Test_Output.txt
fi
done < "$filename"
Several items:
I assume that you have been using single back-quotes in the assignments, and not literally sub= echo $line | cut -d '/' -f1, as this would have certainly failed. Alternatively, you can also use sub=$(), as in $(echo $line | cut -d '/' -f1)
The conditions in an if clause need to be encompassed by single or double [], like this: if [[ $length -ge 4 ]] && [[ $length -le 10 ]];
Which brings me to the next point: <= doesn't reliably work in bash. Just use -ge for "greater or equal" and -le for "less or equal".
If your line does not contain any / characters, in your version sub will contain the whole line. This might not be what you want, so I'd advise to also add the -s flag to cut.
You don't need somevar=$(echo $someothervar). Just use somevar=$someothervar
Here's a version that works:
#!/usr/bin/env bash
filename="Test_Input.txt"
while read -r line
do
sub=$(echo $line | cut -s -d '/' -f 1)
length=${#sub}
if [[ $length -ge 4 ]] && [[ $length -le 10 ]];
then echo $sub >> Test_Output.txt
fi
done < "$filename"
Of course, you could also just use sed:
sed -n -r '/^[^/]{4,10}\// s;/.*$;;p' Test_Input.txt > Test_Output.txt
Explanation:
-n Don't print anything unless explicitly marked for printing.
-r Use the extended regex
/<searchterm>/ <operation> Search for lines that match a certain criteria, and perform this operation:
Searchterm is: ^[^/]{4,10}\/ From the beginning of the line, there should be between 4 and 10 non-slash characters, followed by the slash
Operation is: s;/.*$;;p replace everything between the first slash and the end of the line with nothing, then print.
awk is the best tool for this
awk -F/ 'length($1) >= 4 && length($1) <= 10 {print $1} > newfile

How to check if word is in alphabetical order

I 'd like to find a bash only (no sed, awk, perl, ...) for finding out if a word is in alphabetical order, in other words every letter is.
example:
bdjkz is true,
ahjmno is true,
sdgla is false.
I'm already struggling just comparing ascii values for characters, so if anyone could point me in the right direction for that it would help a lot!
Thanks
Pure bash solution (no external tool used), using Parameter Expansion to address characters inside strings:
function compare () {
word=$1
for (( pos=0; pos<${#word}-1; pos++ )) ; do
[[ ${word:pos:1} < ${word:pos+1:1} ]] || return 1
done
return 0
}
Tested with
for word in bdjkz ahjmno sdgla ; do
if compare $word ; then
echo $word ordered
else
echo $word not ordered
fi
done
If you can utilize other command line tools (but not awk, sed, perl), you can try:
[[ "YOURSTRING" = "$(echo "YOURSTRING" | grep -o '.' | sort -n |tr -d '\n')" ]] && \
echo "Alphabetic order"
[[ ... ]] is testing the expresion
"YOURSTRING" = string comparison
"$( ... )" capture the inner workings output in a string
echo "YOURSTRING" | grep -o '.' print every character on a line from "YOURSTRING" (-o '.': print only the matches for any single character - NOTE: you might need a new version of grep for this option)
... sort -n | sort the output from 4.
... tr -d '\n' rejoin the characters from 5. (by deleting the trailing new line characters)
You can use:
p='bdjkz'
q=$(fold -w1 <<< "$p"|sort|tr -d "\n")
[[ "$p" == "$q" ]] && echo "in alphabetical order" || echo "not in alphabetical order"
s=($(echo "existingString" | grep -o .)) # put each character of input string in an array.
k=($(printf '%s\n' "${s[#]}" | sort)) # sorts the input string
if [[ "${s[*]}" == "${k[*]}" ]]; then # comparing the input string array with sorted array
echo "alphabetical"
else
echo "not alphabetical"
fi

Resources