I have a set of valid characters [0-9a-z_] and a variable that is assigned one of these characters. What I want to do is to be able to increment that variable to the next in the set.
If need be I can handle the "special" cases where it would increment from '9' to 'a' and 'z' to '_', but I can't figure out how to increment letters.
#!/bin/bash
y=b
echo $y # this shows 'b'
y=$((y+1))
echo $y # this shows '1', but I want it to be 'c'
y=b
echo "$y" # this shows 'b'
y=$(echo "$y" | tr "0-9a-z" "1-9a-z_")
echo "$y" # this shows 'c'
Note that this does not handle the case where $y = "_" (not sure what you want then, and in any case it'll probably require separate handling), and if $y is more than one character long it'll "increment" all of them (i.e. "10" -> "21", "09g" -> "1ah", etc).
Maybe this can be a solution:
a=({0..9} {a..z} _)
echo ${a[*]}
yc=11
echo ${a[yc]}
((++yc))
echo ${a[yc]}
echo ${a[++yc]}
#Alternative
declare -A h
# Fill the has point to the next character
for((i=0;((i+1))<${#a[*]};++i)) { h[${a[i]}]=${a[i+1]};}
y=b
echo $y, ${h[$y]}
Output:
0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z _
b
c
d
b, c
for those who would like to print incremented letter by execution of a function:
ALPHA=( {A..Z} )
alpha_increment () { echo ${ALPHA[${i:-0}]}; ((i++)) ;}
alpha_increment
A
alpha_increment
B
alpha_increment
C
You can start with this:
echo 0x$(( $(printf "%x" "'b'") + 1)) | xxd -r
I wrote this for a project, it uses the chr and ord fucntions(also found here somewhere) and some pure bash(only external called in the function is tr), if you are doing more than 100 characters I would use something else, but for short strings in my testing its actually slightly faster than python.
Also this script lower cases any input, you will have to modify it for upper case.
after putting these functions in your script(or cut and paste into a shell) you can just do
inc_string abz9z9
and get back.
aca0a0
chr() {
[ "$1" -lt 256 ] || return 1
printf "\\$(printf '%03o' "$1")"
}
ord() {
LC_CTYPE=C printf '%d' "'$1"
}
inc_string ()
{
string="$1";
lcstring=$(echo $string | tr '[:upper:]' '[:lower:]');
for ((position=$((${#lcstring}-1));position>=0;position--));do
if [ "${lcstring:position:1}" = 'z' ]; then
if [ "$position" -eq "$((${#lcstring}-1))" ]; then
newstring="${lcstring:0:$(($position))}a";
lcstring="$newstring";
elif [ "$position" -eq "0" ]; then
newstring="a${lcstring:$((position+1))}";
echo $newstring;
break;
else
newstring="${lcstring:0:$(($position))}a${lcstring:$((position+1))}";
lcstring="$newstring";
fi
elif [ "${lcstring:position:1}" = '9' ]; then
if [ "$position" -eq "$((${#lcstring}-1))" ]; then
newstring="${lcstring:0:$(($position))}0";
lcstring="$newstring";
elif [ "$position" -eq "0" ]; then
newstring="0${lcstring:$((position+1))}";
echo $newstring;
break;
else
newstring="${lcstring:0:$(($position))}0${lcstring:$((position+1))}";
lcstring="$newstring";
fi
else
if [ "$position" -eq "$((${#lcstring}-1))" ]; then
newstring="${lcstring:0:$(($position))}$(chr $(($(ord ${lcstring:position})+1)))";
echo $newstring;
break;
elif [ "$position" -eq "0" ]; then
newstring="$(chr $(($(ord ${lcstring:position})+1)))${lcstring:$((position+1))}";
echo $newstring;
break;
else
newstring="${lcstring:0:$(($position))}$(chr $(($(ord ${lcstring:position})+1)))${lcstring:$(($position+1))}";
echo $newstring;
break;
fi
fi
done
}
Related
I'm implementing a merge sort algorithm in bash, but looks like it loops forever and gives error on m1 and m2 subarrays. It's a bit hard to stop loop in conditions since I have to use echo and not return. Anyone have any idea why this happens?
MergeSort (){
local a=("$#")
if [ ${#a[#]} -eq 1 ]
then
echo ${a[#]}
elif [ ${#a[#]} -eq 2 ]
then
if [ ${a[0]} -gt ${a[1]} ]
then
local t=(${a[0]} ${a[1]})
echo ${t[#]}
else
echo ${a[#]}
fi
else
local p=($(( ${#a[#]} / 2 )))
local m1=$(MergeSort "${a[#]::p}")
local m2=$(MergeSort "${a[#]:p}")
local ret=()
while true
do
if [ "${#m1[#]}" > 0 ] && [ "${#m2[#]}" > 0 ]
then
if [ ${m1[0]} <= ${m2[0]} ]
then
ret+=(${m1[0]})
m1=${m1[#]:1}
else
ret+=(${m2[0]})
m2=${m2[#]:1}
fi
elif [ ${#m1[#]} > 0 ]
then
ret+=(${ret[#]} ${m1[#]})
unset m1
elif [ ${#m2[#]} > 0 ]
then
ret+=(${ret[#]} ${m2[#]})
unset m2
else
break
fi
done
fi
echo ${ret[#]}
}
a=(6 5 6 4 2)
b=$(MergeSort "${a[#]}")
echo ${b[#]}
There are multiple issues in your shell script:
you should use -gt instead of > for numeric comparisons on array lengths
<= is not a supported string comparison operator. You should use < and quote it as '<', or better use '>' and transpose actions to preserve sort stability.
there is no need for local t, and your code does not swap the arguments. Just use echo ${a[1]} ${a[0]}
you must parse the result of recursive calls to MergeSort as arrays: local m1=($(MergeSort "${a[#]::p}"))
when popping initial elements from m1 and m2, you must reparse as arrays: m1=(${m1[#]:1})
instead of ret+=(${ret[#]} ${m1[#]}) you should just append the elements with ret+=(${m1[#]}) and instead of unset m1, you should break from the loop. As a matter of fact, if either array is empty you should just append the remaining elements from both arrays and break.
furthermore, the while true loop should be simplified as a while [ ${#m1[#]} -gt 0 ] && [ ${#m2[#]} -gt 0 ] loop followed by the tail handling.
the final echo ${ret[#]} should be moved inside the else branch of the last if
to handle embedded spaces, you should stringize all expansions but as the resulting array is expanded with echo embedded spaces that appear in the output are indistinguishable from word breaks. There is no easy workaround for this limitation.
Here is a modified version:
#!/bin/bash
MergeSort (){
local a=("$#")
if [ ${#a[#]} -eq 1 ]; then
echo "${a[#]}"
elif [ ${#a[#]} -eq 2 ]; then
if [ "${a[0]}" '>' "${a[1]}" ]; then
echo "${a[1]}" "${a[0]}"
else
echo "${a[#]}"
fi
else
local p=($(( ${#a[#]} / 2 )))
local m1=($(MergeSort "${a[#]::p}"))
local m2=($(MergeSort "${a[#]:p}"))
local ret=()
while [ ${#m1[#]} -gt 0 ] && [ ${#m2[#]} -gt 0 ]; do
if [ "${m1[0]}" '>' "${m2[0]}" ]; then
ret+=("${m2[0]}")
m2=("${m2[#]:1}")
else
ret+=("${m1[0]}")
m1=("${m1[#]:1}")
fi
done
echo "${ret[#]}" "${m1[#]}" "${m2[#]}"
fi
}
a=(6 5 6 4 2 a c b c aa 00 0 000)
b=($(MergeSort "${a[#]}"))
echo "${b[#]}"
Output: 0 00 000 2 4 5 6 6 a aa b c c
I am using Hacker Rank challenges to teach myself BASH, and I'm in need of some advice.
I'm specifically trying to solve this challenge: Apple and Oranges by nabila_ahmed
I need to read in multiple lines of ints separated by spaces, on multiple lines. I decided to use awk to do this because it seems a lot more efficient in memory storage than using read. (I tried a couple of solutions using read and they timed out, because the test cases are really big.)
Example input:
7 11
5 15
3 2
-2 2 1
5 -6
This is my first attempt in bash and it timed out:
row=0
while read line || [[ -n $line ]]; do
if [ "$row" -eq 0 ]
then
column=0
for n in $line; do
if [ "$column" -eq 0 ]
then
housePos1=$n
elif [ "$column" -eq 1 ]
then
housePos2=$n
fi
((column++))
done
# Calculate house min and max
if [ "$housePos1" -gt "$housePos2" ]
then
minHousePos=$housePos2
maxHousePos=$housePos1
else
minHousePos=$housePos1
maxHousePos=$housePos2
fi
elif [ "$row" -eq 1 ]
then
column=0
for n in $line; do
if [ "$column" -eq 0 ]
then
appleTreePos=$n
elif [ "$column" -eq 1 ]
then
orangeTreePos=$n
fi
((column++))
done
elif [ "$row" -eq 3 ]
then
applesInHouse=0
for n in $line; do
# Calculate the apple's position
let applePos=$((appleTreePos + n))
# If the apple's position is within the houses position, count it
if [ "$applePos" -ge "$minHousePos" ] && [ "$applePos" -le "$maxHousePos" ]
then
((applesInHouse++))
fi
done
elif [ "$row" -eq 4 ]
then
orangesInHouse=0
for n in $line; do
# Calculate the apple's position
let orangePos=$((orangeTreePos + n))
# If the apple's position is within the houses position, count it
if [ "$orangePos" -ge "$minHousePos" ] && [ "$orangePos" -le "$maxHousePos" ]
then
((orangesInHouse++))
fi
done
fi
((row++))
done
echo "$applesInHouse"
echo "$orangesInHouse"
Here is my second attempt in bash, even more of the solutions timed out:
x=0;y=0;read -r s t;read -r a b;read -r m n;
for i in `seq 1 $m`; do
if [ "$i" -lt "$m" ]
then
read -d\ z
else
read -r z
fi
if [ "$((a+z))" -ge "$s" ] && \
[ "$((a+z))" -le "$t" ]
then
((x++))
fi
done
for i in `seq 1 $n`; do
if [ "$i" -lt "$n" ]
then
read -d\ z
else
read -r z
fi
if [ "$((b+z))" -ge "$s" ] && \
[ "$((b+z))" -le "$t" ]
then
((y++))
fi
done
echo $x; echo $y
Here's where I am at in debugging my solution using awk...
awk -v RS='[-]?[0-9]+' \
'{
if(word==$1) {
counter++
if(counter==1){
s=RT
}else if(counter==2){
t=RT
}else if(counter==3){
a=RT
}else if(counter==4){
b=RT
}else if(counter==5){
m=RT
}else if(counter==6){
n=RT
}else{
counter2++
if(counter2<=m){
print "Apples:"
print a+RT
print a+RT>=s
print a+RT<=t
applecount++
}
if(counter2>m && counter2<=m+n){
print "Oranges:"
print b+RT
print b+RT>=s
print b+RT<=t
orangecount++
}
}
}else{
counter=1
word=$1
}
}
END {
print "Total Counts:"
print applecount
print orangecount
}
'
Here is the output from that script when using the sample input
Apples:
3
0
0
Apples:
7
1
0 <-- This is the problem! (7 is less than or equal to 11)
Apples:
6
0
0
Oranges:
20
0
0
Oranges:
9
1
0 <-- This is also a problem! (9 is less than or equal to 11)
Total Counts:
3
2
As you can see, I'm getting some of the wrong comparisons...
ANSWER
(mostly courtesy of #glenn-jackman)
apples_oranges() {
local s t a b m n d
local -a apples oranges
local na=0 nb=0
{
read s t
read a b
read m n
read -a apples
read -a oranges
} < "$1"
for d in "${apples[#]}"; do
(( s <= a+d && a+d <= t )) && ((na++))
done
echo $na
for d in "${oranges[#]}"; do
(( s <= b+d && b+d <= t )) && ((nb++))
done
echo $nb
}
apples_oranges /dev/stdin
I'd do this with bash
apples_oranges() {
local s t a b m n d
local -a apples oranges
local na=0 nb=0
{
read s t
read a b
read m n # unused
read -a apples
read -a oranges
} < "$1"
for d in "${apples[#]}"; do
(( a+d >= s )) && ((na++))
done
echo $na
for d in "${oranges[#]}"; do
(( b-d <= t )) && ((nb++))
done
echo $nb
}
apples_oranges input.txt
this may get you started...
$ awk '
NR==1{split($0,house)}
NR==2{split($0,trees)}
NR==3{split($0,counts)}
NR==4{split($0,apples)}
NR==5{split($0,oranges)}
END{for(i in apples)
if(trees[1]+apples[i]>=house[1] && trees[1]+apples[i]<=house[2]) a++; print a}' file
So I have fixed my first issue with the script taking input but now no matter what letter I enter it only adds the numbers.
here is the code. any help would be greatly appreciated.
#!/bin/bash
add() {
expr $x + $y
}
sub() {
expr $x - $y
}
mult() {
expr $x * $y
}
div() {
expr $x / $y
}
echo "Enter a for add, s for subtract, m for multiply or d for divide and 2 numbers"
read choice x y
if [ $choice=="a" ]
then
add
else
if [ $choice == "s" ]
then
sub
else
if [ $choice == "m" ]
then
mult
else
if [ $choice == "d" ]
then
div
fi
fi
fi
fi
It seems like you want to get x and y from first and second arguments ( $1 and $2 ) and to read the operation ( a, s, d, m ) from stdin.
I modified your code a bit, to overcome the problems in the original script and to provide the result based on my assumptions:
#!/bin/bash
# First number.
x=$1
# Second number.
y=$2
# Result of either addition, subtraction, division or multiplication of $x and $y.
result=0
# Reads operation from user.
read -ep "Enter a for add, s for subtract, m for multiply or d for divide: " operation
case $operation in
a) result=$(( x + y ));;
s) result=$(( x - y ));;
d) result=$(( x / y ));;
m) result=$(( x * y ));;
*) printf '%s: %s\n' "$operation" "Unknown operation" >&2; exit 1;;
esac
printf 'result: %s\n' "$result"
Usage example: ( script name is sof.sh )
./sof.sh 5 4
Enter a for add, s for subtract, m for multiply or d for divide: a
result: 9
./sof.sh 5 4
Enter a for add, s for subtract, m for multiply or d for divide: m
result: 20
./sof.sh 5 4
Enter a for add, s for subtract, m for multiply or d for divide: s
result: 1
./sof.sh 5 4
Enter a for add, s for subtract, m for multiply or d for divide: d
result: 1
P.S.
Please note the following:
expr is a program used in ancient shell code to do math. In POSIX shells like bash, use $(( expression )). In bash, ksh88+, mksh/pdksh, or zsh, you can also use (( expression )) or 'let
expression'.
Though not used originaly in the script, while programming in Bash it is worth knowing that [[ is a bash keyword similar to (but more powerful than) the [ command. See this Bash FAQ and Test and conditionals.
Unless you're writing for POSIX sh, it is recommended to use [[.
First of all, you want the script to read the values from the standard input, but you are recovering it from the arguments.
Second, you are not passing parameters to the functions.
Third, you are not using parameters inside the functions.
Fourth, you are not letting spaces between operators when using expr.
NOTE: Rany Albeg Wein remarked that this bash guide is outdated, and he recommends this one. Also i recommend the GNU official guide (other formats).
So, assuming that you want to use your script like ./my-script.sh m 2 3 , here is your code, but working:
#!/bin/bash
add() {
expr $1 + $2
}
sub() {
expr $1 - $2
}
mult() {
expr $1 \* $2
}
div() {
expr $1 / $2
}
echo "Enter a for add, s for subtract, m for multiply or d for divide and 2 numbers"
x=$2
y=$3
if [ $1 == "a" ]
then
add $x $y
else
if [ $1 == "s" ]
then
sub $x $y
else
if [ $1 == "m" ]
then
mult $x $y
else
if [ $1 == "d" ]
then
div $x $y
fi
fi
fi
fi
And finally this is your script minimally modified to read the data from the standard input:
#!/bin/bash
add() {
echo "Result:"
expr $1 + $2
}
sub() {
echo "Result:"
expr $1 - $2
}
mult() {
echo "Result:"
expr $1 \* $2
}
div() {
echo "Result:"
expr $1 / $2
}
echo "Enter a for add, s for subtract, m for multiply or d for divide and 2 numbers"
read operation
echo "Read first parameter"
read x
echo "Read second parameter"
read y
if [ $operation == "a" ]
then
add $x $y
else
if [ $operation == "s" ]
then
sub $x $y
else
if [ $operation == "m" ]
then
mult $x $y
else
if [ $operation == "d" ]
then
div $x $y
fi
fi
fi
fi
Also, if you had some troubles, you could add debugging messages to the script just setting #!/bin/bash -xv at the beginning of the script.
I'd have a couple of possible (say string) values: a b c. I'd like a to switch from one to another in a circular way:
if [ "$value" == "a" ]; then value=b
elif [ "$value" == "b"]; then value=c
elif [ "$value" == "c"]; then value=a
fi
With method like this, Can this be done in a more efficient way, so that I could support many options, like a b c ... z?
It can be done like this:
get_next()
{
__current_i=${__current_i:- 0} # global variable that saves index of current value
[ $__current_i -ge $# ] && __current_i=0 # if we exceed the number of values, then reset to zero
shift $__current_i # move to current value
((__current_i++)) # increment index
echo $1
}
val="a b c d"
get_next $val
get_next $val
get_next $val
get_next $val
get_next $val
You can use bash arrays
vals=('a' 'b' 'c')
value=$1
max=$(( ${#vals[#]} - 1 ))
count=0
while [ "$value" != "${vals[count]}" ]
do
count=$(( $count + 1 ))
done
if [ $count -eq $max ]; then
count=0
else
count=$(( $count + 1 ))
fi
echo ${vals[count]}
Why so complicated?
This will do what you want:
while true; do
for x in a b c; do
echo "$x"
done
done
I have some code (Bash Script .sh) that generates me different combinations of characters.
When the variable "chars" contains the star with "\" (*), then generates a string with slash and star. But when I delete a slash in front of the star, then throws me a string of file names.
Does anyone have any idea how I can generate the correct string with a star?
min_length="1"
max_length="2"
chars="\` ~ ! # # $ % ^ & \* ( ) - _ = + [ { ] } \ | ; : ' \" , < . > / ?"
generateCombinationsOfString() {
for c in $chars; do
nc=$[$nc+1]
ch[$nc]=$c
done
for x in `seq 1 $[$max_length+1]`; do
if [ $min_length -ge $x ]; then
ci[$x]=1
else
ci[$x]=0
fi
done
for clen in `seq $min_length $max_length`; do
while [ ${ci[$[$clen+1]]} -ne 1 ]; do
wrd=""
for x in `seq $clen -1 1`; do
wrd=$wrd${ch[${ci[$x]}]}
done
echo "${wrd}"
ci[1]=$[${ci[1]}+1]
for x in `seq 1 $clen`; do
if [ ${ci[$x]} -gt $nc ]; then
ci[$x]=1
ci[$[$x+1]]=$[${ci[$[$x+1]]}+1]
fi
done
done
done
}
generateCombinationsOfString
The problem is in the following loop
for c in $chars ; do
cf.
chars='1 2 * 3'
for c in $chars ; do
echo "$c"
done
Pathname expansion happens after variable expansion.
To prevent it, use an array:
#! /bin/bash
chars=(1 2 \* 3)
for c in "${chars[#]}" ; do
echo "$c"
done