Generate random passwords in shell with one special character - bash

I have the following code:
</dev/urandom tr -dc 'A-Za-z0-9##$%&_+=' | head -c 16
which is randomly generating passwords perfectly.
I want two changes:
It should only contain one special character listed above
It should choose a random length
I tried with length = $(($RANDOM%8+9))
then putting length as
</dev/urandom tr -dc 'A-Za-z0-9##$%&_+=' | head -c$length
but got no positive result.

#! /bin/bash
chars='##$%&_+='
{ </dev/urandom LC_ALL=C grep -ao '[A-Za-z0-9]' \
| head -n$((RANDOM % 8 + 9))
echo ${chars:$((RANDOM % ${#chars})):1} # Random special char.
} \
| shuf \
| tr -d '\n'
LC_ALL=C prevents characters like ř from appearing.
grep -o outputs just the matching substring, i.e. a single character.
shuf shuffles the lines. I originally used sort -R, but it kept the same characters together (ff1#22MvbcAA).

## Objective: Generating Random Password:
function random_password () {
[[ ${#1} -gt 0 ]] && { local length=${1}; } || { local length=16; }
export DEFAULT_PASSWORDLENGTH=${length};
export LC_CTYPE=C;
local random="$(
tr -cd "[:graph:]" < /dev/urandom \
| head -c ${length} \
| sed -e 's|\`|~|g' \
-e 's|\$(|\\$(|g';
)";
echo -e "${random}";
return 0;
}; alias random-password='random_password';
$ random-password 32 ;
)W#j*deZ2#eMuhU4TODO&eu&r)&.#~3F
# Warning: Do not consider these other options
# date +%s | sha256sum | base64 | head -c 32 | xargs -0;
# Output: MGFjNDlhMTE2ZWJjOTI4OGI4ZTFiZmEz
# dd if=/dev/urandom count=200 bs=1 2>/dev/null \
# | tr -cd "[:graph:]" \
# | cut -c-${length} \
# | xargs -0;
# Output: AuS*D=!wkHR.4DZ_la

Related

Trouble generating random hex numbers using /dev/urandom in bash

I'm trying to generate a random hex number with an specified length ($length) using the following command:
head -c $length /dev/urandom | xxd -p -u -c $length | tr -d '[:space:]\\'
I've noticed that head -c $length /dev/urandom actually prints the double of the $length value. So if $length=1 the output will be 2 characters long. How can I fix this?
Also why does the input number on xxd -p -u -c $length seems not to affect on the output? meaning I can use either:
head -c 4 /dev/urandom | xxd -p -u -c 20000 | tr -d '[:space:]\\'
or
head -c 4 /dev/urandom | xxd -p -u -c 4 | tr -d '[:space:]\\'
and it will print 8 characters in both cases.

How to find all non-dictionary words in a file in bash/zsh?

I'm trying to find all words in a file that don't exist in the dictionary. If I look for a single word the following works
b=ther; look $b | grep -i "^$b$" | ifne -n echo $b => ther
b=there; look $b | grep -i "^$b$" | ifne -n echo $b => [no output]
However if I try to run a "while read" loop
while read a; do look $a | grep -i "^$a$" | ifne -n echo "$a"; done < <(tr -s '[[:punct:][:space:]]' '\n' <lotr.txt |tr '[:upper:]' '[:lower:]')
The output seems to contain all (?) words in the file. Why doesn't this loop only output non-dictionary words?
Regarding ifne
If stdin is non-empty, ifne -n reprints stdin to stdout. From the manpage:
-n Reverse operation. Run the command if the standard input is empty
Note that if the standard input is not empty, it is passed through
ifne in this case.
strace on ifne confirms this behavior.
Alternative
Perhaps, as an alternative:
1 #!/bin/bash -e
2
3 export PATH=/bin:/sbin:/usr/bin:/usr/sbin
4
5 while read a; do
6 look "$a" | grep -qi "^$a$" || echo "$a"
7 done < <(
8 tr -s '[[:punct:][:space:]]' '\n' < lotr.txt \
9 | tr '[A-Z]' '[a-z]' \
10 | sort -u \
11 | grep .
12 )

Wants to generate a random password on Linux server of length 15 characters

String must contain at least 3 uppercase, 3 lowercase, 3 digits and at least 3 special characters.
I searched a lot, but not able to get the required solution.
head /dev/urandom | tr -dc 'A-Za-z0-9-_' | head -c15
This is what I found, but I am able to get the random combination of all.
What i needed is it must output at least 3 characters from each set.
Try this script:
#!/bin/sh
export LC_ALL=C
upp=$(tr -dc 'A-Z' </dev/urandom | head -c3)
low=$(tr -dc 'a-z' </dev/urandom | head -c3)
dig=$(tr -dc '0-9' </dev/urandom | head -c3)
spe=$(tr -dc '!-/' </dev/urandom | head -c3)
res=$(tr -dc '!-}' </dev/urandom | head -c3)
echo "$upp$low$dig$spe$res"
First, the statement export LC_ALL=C makes sure that we are using just plain ASCII. This eliminates potential issues associated with unicode characters.
Next, the variable upp is assigned to 3 upper-case characters. Similarly, low gets three lower-case, $dig gets three digits, spe gets three special characters, and res gets 3 random characters. The echo statement combines all four variables ands prints them.
The above prints the upper case characters first, lower case second, etc. If you want this order mixed up, replace the last line above with:
echo "$upp$low$dig$spe$res" | sed 's/./&\n/g' | shuf | tr -d '\n'
This script uses openssl-rand to get strings of 15 letters/numbers/special characters until the loop breaks when all conditions are met and the password is printed.
#!/bin/bash
until [ -z "$pw+x" ]
do
pw=$(openssl rand -base64 32 | cut -c1-15) &&\
[[ $(sed "s/[^[:upper:]]//g" <<< $pw | wc -c) -gt 3 ]] &&\
[[ $(sed "s/[^[:lower:]]//g" <<< $pw | wc -c) -gt 3 ]] &&\
[[ $(sed "s/[^0-9]//g" <<< $pw | wc -c) -gt 3 ]] &&\
[[ $(sed "s/[[:alnum:]]//g" <<< $pw | wc -c) -gt 3 ]] && break
done
echo "$pw"

BASH Finding palindromes in a .txt file

I have been given a .txt file in which we have to find all the palindromes in the text (must have at least 3 letters and they cant be the same letters e.g. AAA)
it should be displayed with the first column being the amount of times it appears and the second being the word e.g.
123 kayak
3 bob
1 dad
#!/bin/bash
tmp='mktemp'
awk '{for(x=1;$x;++x)print $x}' "${1}" | tr -d [[:punct:]] | tr -s [:space:] | sed -e 's/#//g' -e 's/[0-9]*//g'| sed -r '/^.{,2}$/d' | sort | uniq -c -i > tmp1
This outputs the file as it should do, ignoring case, words less than 3 letters, punctuation and digits.
However i am now stump on how to pull out the palindromes from this, i thought a temp file might be the way, just don't know where to take it.
any help or guidance is much appreciated.
# modify this to your needs; it should take your input on stdin, and return one word per
# line on stdout, in the same order if called more than once with the same input.
preprocess() {
tr -d '[[:punct:][:digit:]#]' \
| sed -E -e '/^(.)\1+$/d' \
| tr -s '[[:space:]]' \
| tr '[[:space:]]' '\n'
}
paste <(preprocess <"$1") <(preprocess <"$1" | rev) \
| awk '$1 == $2 && (length($1) >= 3) { print $1 }' \
| sort | uniq -c
The critical thing here is to paste together your input file with a stream that has each line from that input file reversed. This gives you two separate columns you can compare.

How to assign output of multiple shell commmands to variable when using tee?

I want to tee and get the results from multiple shell commands connected in the pipeline. I made a simple example to explain the point. Suppose I wanna count the numbers of 'a', 'b' and 'c'.
echo "abcaabbcabc" | tee >(tr -dc 'a' | wc -m) >(tr -dc 'b' | wc -m) >(tr -dc 'c' | wc -m) > /dev/null
Then I tried to assign the result from each count to a shell variable, but they all end up empty.
echo "abcaabbcabc" | tee >(A=$(tr -dc 'a' | wc -m)) >(B=$(tr -dc 'b' | wc -m)) >(C=$(tr -dc 'c' | wc -m)) > /dev/null && echo $A $B $C
What is the right way to do it?
Use files. They are the single most reliable solution. Any of the commands may need different time to run. There is no easy way to synchronize command redirections. Then most reliable way is to use a separate "entity" to collect all the data:
tmpa=$(mktemp) tmpb=$(mktemp) tmpc=$(mktemp)
trap 'rm "$tmpa" "$tmpb" "$tmpc"' EXIT
echo "abcaabbcabc" |
tee >(tr -dc 'a' | wc -m > "$tmpa") >(tr -dc 'b' | wc -m > "$tmpb") |
tr -dc 'c' | wc -m > "$tmpc"
A=$(<"$tmpa")
B=$(<"$tmpb")
C=$(<"$tmpc")
rm "$tmpa" "$tmpb" "$tmpc"
trap '' EXIT
Second way:
You can prepend the data from each stream with a custom prefix. Then sort all lines (basically, buffer them) on the prefix and then read them. The example script will generate only a single number from each process substitution, so it's easy to do:
read -r A B C < <(
echo "abcaabbcabc" |
tee >(
tr -dc 'a' | wc -m | sed 's/^/A /'
) >(
tr -dc 'b' | wc -m | sed 's/^/B /'
) >(
tr -dc 'c' | wc -m | sed 's/^/C /'
) >/dev/null |
sort |
cut -d' ' -f2 |
paste -sd' '
)
echo A="$A" B="$B" C="$C"
Using temporary files with flock to synchronize the output of child processes could look like this:
tmpa=$(mktemp) tmpb=$(mktemp) tmpc=$(mktemp)
trap 'rm "$tmpa" "$tmpb" "$tmpc"' EXIT
echo "abcaabbcabc" |
(
flock 3
flock 4
flock 5
tee >(
tr -dc 'a' | wc -m |
{ sleep 0.1; cat; } > "$tmpa"
# unblock main thread
flock -u 3
) >(
tr -dc 'b' | wc -m |
{ sleep 0.2; cat; } > "$tmpb"
# unblock main thread
flock -u 4
) >(
tr -dc 'c' | wc -m |
{ sleep 0.3; cat; } > "$tmpc"
# unblock main thread
flock -u 5
) >/dev/null
# wait for subprocesses to finish
# need to re-open the files to block on them
(
flock 3
flock 4
flock 5
) 3<"$tmpa" 4<"$tmpb" 5<"$tmpc"
) 3<"$tmpa" 4<"$tmpb" 5<"$tmpc"
A=$(<"$tmpa")
B=$(<"$tmpb")
C=$(<"$tmpc")
declare -p A B C
You can use this featured letter frequency analysis
#!/usr/bin/env bash
declare -A letter_frequency
while read -r v k; do
letter_frequency[$k]="$v"
done < <(
grep -o '[[:alnum:]]' <<<"abcaabbcabc" |
sort |
uniq -c
)
for k in "${!letter_frequency[#]}"; do
printf '%c = %d\n' "$k" "${letter_frequency[$k]}"
done
Output:
c = 3
b = 4
a = 4
Or to only assign $A, $B and $C as in your example:
#!/usr/bin/env bash
{
read -r A _
read -r B _
read -r C _
}< <(
grep -o '[[:alnum:]]' <<<"abcaabbcabc" |
sort |
uniq -c
)
printf 'a=%d\nb=%d\nc=%d\n' "$A" "$B" "$C"
grep -o '[[:alnum:]]': split each alphanumeric character on its own line
sort: sort lines of characters
uniq -c: count each instance and output count and character for each
< <( command group; ): the output of this command group is for stdin of the command group before
If you need to count occurrence of non-printable characters, newlines, spaces, tabs, you have to make all these commands output and deal with null delimited lists. It can sure be done with the GNU versions of these tools. I let it to you as an exercise.
Solution to the count arbitrary characters except null:
As demonstrated, works also with Unicode.
#!/usr/bin/env bash
declare -A character_frequency
declare -i v
while read -d '' -r -N 8 v && read -r -d '' -N 1 k; do
character_frequency[$k]="$v"
done < <(
grep --only-matching --null-data . <<<$'a¹bc✓ ✓\n\t\t\u263A☺ ☺ aabbcabc' |
head --bytes -2 | # trim the newline added by grep
sort --zero-terminated | # sort null delimited list
uniq --count --zero-terminated # count occurences of char (null delim)
)
for k in "${!character_frequency[#]}"; do
printf '%q = %d\n' "$k" "${character_frequency[$k]}"
done
Output:
$'\n' = 1
$'\t' = 2
☺ = 3
\ = 7
✓ = 2
¹ = 1
c = 3
b = 4
a = 4

Resources