Shell Scripting - Check if numbers on an argument are unique - linux

I am writing a script that takes a 3-digit number and checks if they are unique and produces files with each digit, for example if I run "myscript 123" it will generate three files: file1, file2, and file3 BUT if I ran "my script 121" it will generate file1 and file2
I need help to look at the argument and compare each digit, could anyone give me a hand?
This is what I have written so far:
if [ $1 -gt 99 ] && [ $1 -lt 1000 ]
then
echo "Your three digit number: $1"
else
echo "please restart and enter a 3-digit number"
exit
fi

My biggest challenge though is to check $1 and see what each digit is, I am clueless
There are several ways to retrieve each characted from a string. For example, you can split them up into an array with the help of fold:
$ IN="123"
$ DIGITS=($(echo $IN | fold -w1))
$ echo ${DIGITS[0]}
1
$ echo ${DIGITS[1]}
2
Or you can use variable substring extraction (see docs):
$ IN="123"
$ echo ${IN:0:1}
1
$ echo ${IN:1:1}
2
Or, you can use let to perform basic modulo operations:
$ IN="123"
$ let A="IN % 10" # get last digit (3rd)
$ ((IN /= 10)) # divide by 10 (which discards last digit)
$ let B="IN % 10" # get last digit (2nd)
$ ((IN /= 10)) # divide by 10 (which discards last digit)
$ let C="IN % 10" # get last digit (1st)
$ echo $A
3
$ echo $B
2
$ echo $C
1

You can use the modulo and divide operators to extract the digits, then just compare them. Or you can use a combination of "echo", "head, and "tail" to extract the digits. For example:
digit2=$(echo $1|head -c 2|tail -c 1)

This will just generate the file names, and I'm addressing this from a different angle.
We're talking about digits here. So you will create no more than ten files, max.
So, instead of isolating each digit, we just look for each one and see if it's present in the number given:
function files_for_digits {
[[ $1 != [0-9][0-9][0-9] ]] &&
echo "specify a three digit number" >&2 && return 1
typeset i
for i in {0..9}; do
[[ $1 == *$i* ]] && echo file$i
done
}

Related

How do I split a string on a pattern at the linux bash prompt and return the last instance of my pattern and everything after

This is my first question on StackOverflow, I hope it's not too noob for this forum. Thanks for your help in advance!!!
[PROBLEM]
I have a Linux bash variable in my bash script with the below content:
[split]
this is a test 1
[split]
this is a test 2
[split]
this is a test 3
this is a test 4
this is a test 5
How can I split this file on the string "[split]" and return the last section after the split?
this is a test 3
this is a test 4
this is a test 5
The last section can vary in length but it is always at the end of the "string" / "file"
Using awk, set record separator to the regular expression representing the split string, print the last record at END.
gawk 'BEGIN{ RS="[[]split[]]" } END{ print $0 }' tmp/test.txt
Result assuming input coming from a file:
this is a test 3
this is a test 4
this is a test 5
How about this ? :)
FILE="test.txt"
NEW_FILE="test_result.txt"
SPLIT="split"
while read line
do
if [[ $line == $SPLIT ]]
then
$(rm ${NEW_FILE})
else
$(echo -e "${line}" >> ${NEW_FILE})
fi
done < $FILE
#!/bin/bash
s="[split]
this is a test 1
[split]
this is a test 2
[split]
this is a test 3
this is a test 4
this is a test 5"
a=()
i=0
while read -r line
do
a[i]="${a[i]}${line}"$'\n'
if [ "$line" == "[split]" ]
then
let ++i
fi
done <<< "$s"
echo ${a[-1]}
I simply read each line from the string into an array and when I encounter [split] ,I increment the array index.At last,I echo the last element.
EDIT:
if you just want the last part no need for an array too.You could do something like
while read -r line
do
a+="${line}"$'\n'
if [ "$line" == "[split]" ]
then
a=""
fi
done <<< "$s"
echo $a

Compare two strings which differ only slightly

I have the following function to count the number of differences between two Strings:
get_num_diffs_strict() {
local -i diffs=$(cmp -l <(echo "$1") <(echo "$2") 2>/dev/null | wc -l)
local -i lendiff=0
if (( ${#1} > ${#2} ));then
lendiff=$((${#1} - ${#2}))
else
lendiff=$((${#2} - ${#1}))
fi
if (( lendiff > 0 ));then # lines differ in length
(( lendiff-- )) # subtract already accounted EOF character
(( diffs+=lendiff ))
fi
echo $diffs
}
For inputs like
$ get_num_diffs_strict hello somethingelse
$ 13
$ get_num_diffs_strict hello holla
$ 2
the results are acceptable.
For other input strings I would like it to be less strict with what is deemed as a difference. Consider the following cases
$ get_num_diffs_strict hello helo
$ 2
$ get_num_diffs_strict hello heello
$ 3
$ get_num_diffs_strict 0123456789 0123567889
$ 4
The desired output, however, should be 1 in the first two cases and 2 in the last one.
In case 1 and 2 there is only one letter missing or just one additional letter. It should count as one difference. In the last case the second string is missing the number 4 and contains the number 8 twice. These should count only as two differences - like a human would intuitively perceive it.
Is there any program, similar to cmp, that can accomplish that? Alternative solutions (python, perl, etc.) are also acceptable. I am merely trying to avoid reinventing the wheel.

Decrement variables that contain letters

I have a set of valid characters [0-9a-zA-Z] and a variable that is assigned one of these characters. What I want to do is to be able to decrement that variable to the next in the set.
I can't figure out how to decrement letters , it works for numbers only.
#!/bin/bash
test=b
echo $test # this shows 'b'
let test-=1
echo $test # I want it to be 'a'
The advantage of
test=$(tr 1-9a-zA-Z 0-9a-zA-Y <<<"$test")
is that it correctly (I think) decrements a to 9 and A to z. And if that is not the order you want, it is easy to adjust.
See man tr for details. This is the Gnu version of tr; character ranges are not guaranteed by Posix, but most tr implementations have them. <<< "here strings" are also a common extension, which bash implements.
test=$(printf "\\$(printf '%03o' "$(($(printf '%d' "'$test") - 1 ))")")
you could try this:
#!/bin/bash
test=b
if [[ $test == A || $test == a || $test == 0 ]]
then
echo "character already at lowest value"
else
# convert $test to decimal digit
test_digit=$(printf '%d' "'$test")
decremented=$(( test_digit - 1 ))
# print $decremented as a char
printf "\\$(printf '%03o' "$decremented")\n"
fi
reference:
http://mywiki.wooledge.org/BashFAQ/071
If we set a variable (say a) to the whole string of characters:
$ a=$( IFS=''; set -- {0..9} {a..z} {A..Z}; echo "$*"); echo "$a"
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
We may take advantage of the fact that bash "arithmetic" may use a base up to 62 (in the same order as the letters presented).
$ test="A"; echo "${a:$((62#$test-1)):1}"
z
This works only for "one character" (and not zero 0).
It may be expanded to several characters, but that is not being asked.

bash palindrome grep loop if then else missing '

My Syst admin prof just started teaching us bash and he wanted us to write a bash script using grep to find all 3-45 letter palindromes in the linux dictionary without using reverse. And im getting an error on my if statement saying im missing a '
UPDATED CODE:
front='\([a-z]\)'
front_s='\([a-z]\)'
numcheck=1
back='\1'
middle='[a-z]'
count=3
while [ $count -ne "45" ]; do
if [[ $(($count % 2)) == 0 ]]
then
front=$front$front_s
back=+"\\$numcheck$back"
grep "^$front$back$" /usr/share/dict/words
count=$((count+1))
else
grep "^$front$middle$back$" /usr/share/dict/words
numcheck=$((numcheck+1))
count=$((count+1))
fi
done
You have four obvious problems here:
First about a misplaced and unescaped backslash:
back="\\$numcheck$back" # and not back="$numcheck\$back"
Second is that you only want to increment numcheck if count is odd.
Third: in the line
front=$front$front
you're doubling the number of patterns in front! hey, that yields an exponential growth, hence the explosion Argument list too long. To fix this: add a variable, say, front_step:
front_step='\([a-z]\)'
front=$front_step
and when you increment front:
front=$front$front_step
With these fixed, you should be good!
The fourth flaw is that grep's back-references may only have one digit: from man grep:
Back References and Subexpressions
The back-reference \n, where n is a single digit, matches the substring
previously matched by the nth parenthesized subexpression of the
regular expression.
In your approach, we'll need up to 22 back-references. That's too much for grep. I doubt there are any such long palindromes, though.
Also, you're grepping the file 43 times… that's a bit too much.
Try this:
#!/bin/bash
for w in `grep -E "^[[:alnum:]]{3,45}$" /usr/share/dict/words`; do if [[ "$w" == "`echo $w|sed "s/\(.\)/\1\n/g"|tac|tr -d '\012'`" ]]; then echo "$w == is a palindrome"; fi; done
OR
#!/bin/bash
front='\([a-z]\)'
numcheck=1
back='\1'
middle='[a-z]'
count=3
while [ $count -ne "45" ]; do
if [[ $(($count % 2)) == 0 ]]
then
front=$front$front
back="\\$numcheck$back"
grep "^$front$back$" /usr/share/dict/words
else
grep "^$front$middle$back$" /usr/share/dict/words
## Thanks to gniourf for catching this.
numcheck=$((numcheck+1))
fi
count=$((count+1))
## Uncomment the following if you want to see one by one and run script using bash -x filename.sh
#echo Press any key to continue: ; read toratora;
done

Get Nth character with Sed

I'm in the middle of Playing Final Fantasy 7, and I'm at the part where you're in the Library at Shinra HQ and you have to write down the Nth letter--minus the spaces, where Nth is the number in front of the book's title--for every book that doesn't seem to belong in the current room, of which there are 4.
I need a sed script or other command-line to print the title of the book and get the Nth letter in its name.
You don't need sed for that. You can use bash string substitution:
$ book="The Ancients in History"
$ book="${book// /}" # Do global substition to remove spaces
$ echo "${book:13:1}" # Start at position 13 indexed at 0 and print 1 character
H
I figured out how to do it:
echo "The Ancients in History" | sed -r 's/\s//g ; s/^(.{13})(.).*$/\2/'
=> H
NOTESed starts counting at 0 instead of 1, so if you want the 14th letter, ask for the 13th one.
Here's it in a shell script:
#!/bin/sh
if [[ -n "$1" ]]; then # string
if [[ -n "$2" ]]; then # Nth
echo "Getting character" $[$2 - 1]
export Nth=$[$2 - 1]
echo "$1" | sed -r "s/\s//g ; s/^(.{$Nth})(.).*$/\2/";
fi
fi

Resources