Bash post increment inside expression - bash

Consider the following script.
i=1
echo $(printf "%02d" $i)
((i++))
echo $(printf "%02d" $i)
The output is:
01
02
I would like to remove the line in the ((i++)) line in the middle, and have something like the following, but it just prints 01 twice.
i=1
echo $(printf "%02d" $((i++)))
echo $(printf "%02d" $i)
How can I get the same output of 01 followed by 02 without a separate statement to do the increment?
I have already looked at this question, and the solution there does not work for this use case.
Update: I am running in a subshell because the real problem is to do variable assignment:
x=$(printf "%02d" $((i++)))
I changed it to echo originally to simplify the example.

It is possible to assign value of a variable without resorting to subshell by using bash builtin printf.
In your case, you want to increment i while executing the 1st statement then use the same variable on the second statement.
Here's how you do it using bash builtin printf.
i=1
printf -v x "%02d" $((i++))
printf -v y "%02d" $i
You will end up with x=01 and y=02.

Related

bash read strings and output as one key and multiple values

Assuming there is an input:
1,2,C
We are trying to output it as
KEY=1, VAL1=2, VAL2=C
So far trying to modify from here:
Is there a way to create key-value pairs in Bash script?
for i in 1,2,C ; do KEY=${i%,*,*}; VAL1=${i#*,}; VAL2=${i#*,*,}; echo $KEY" XX "$VAL1 XX "$VAL2"; done
Output:
1 XX 2,c XX c
Not entirely sure what the pound ("#") and % here mean above, making the modification kinda hard.
Could any guru enlighten? Thanks.
I would generally prefer easier to read code, as bash can get ugly pretty fast.
Try this:
key_values.sh
#!/bin/bash
IFS=,
count=0
# $* is the expansion of all the params passed in, i.e. $1, $2, $3, ...
for i in $*; do
# '-eq' is checking for equality, i.e. is $count equal to zero.
if [ $count -eq 0 ]; then
echo -n "KEY=$i"
else
echo -n ", VAL${count}=$i"
fi
count=$(( $count + 1 ))
done
echo
Example
key_values.sh 1,2,ABC,123,DEF
Output
KEY=1, VAL1=2, VAL2=ABC, VAL3=123, VAL4=DEF
Expanding on anishsane's comment:
$ echo $1
1,2,3,4,5
$ IFS=, read -ra args <<<"$1" # read into an array
$ out="KEY=${args[0]}"
$ for ((i=1; i < ${#args[#]}; i++)); do out+=", VAL$i=${args[i]}"; done
$ echo "$out"
KEY=1, VAL1=2, VAL2=3, VAL3=4, VAL4=5

Reverse Triangle using shell

OK so Ive been at this for a couple days,im new to this whole bash UNIX system thing i just got into it but I am trying to write a script where the user inputs an integer and the script will take that integer and print out a triangle using the integer that was inputted as a base and decreasing until it reaches zero. An example would be:
reverse_triangle.bash 4
****
***
**
*
so this is what I have so far but when I run it nothing happens I have no idea what is wrong
#!/bin/bash
input=$1
count=1
for (( i=$input; i>=$count;i-- ))
do
for (( j=1; j>=i; j++ ))
do
echo -n "*"
done
echo
done
exit 0
when I try to run it nothing happens it just goes to the next line. help would be greatly appreciated :)
As I said in a comment, your test is wrong: you need
for (( j=1; j<=i; j++ ))
instead of
for (( j=1; j>=i; j++ ))
Otherwise, this loop is only executed when i=1, and it becomes an infinite loop.
Now if you want another way to solve that, in a much better way:
#!/bin/bash
[[ $1 = +([[:digit:]]) ]] || { printf >&2 'Argument must be a number\n'; exit 1; }
number=$((10#$1))
for ((;number>=1;--number)); do
printf -v spn '%*s' "$number"
printf '%s\n' "${spn// /*}"
done
Why is it better? first off, we check that the argument is really a number. Without this, your code is subject to arbitrary code injection. Also, we make sure that the number is understood in radix 10 with 10#$1. Otherwise, an argument like 09 would raise an error.
We don't really need an extra variable for the loop, the provided argument is good enough. Now the trick: to print n times a pattern, a cool method is to store n spaces in a variable with printf: %*s will expand to n spaces, where n is the corresponding argument found by printf.
For example:
printf '%s%*s%s\n' hello 42 world
would print:
hello world
(with 42 spaces).
Editor's note: %*s will NOT generally expand to n spaces, as evidenced by above output, which contains 37 spaces.
Instead, the argument that * is mapped to,42, is the field width for the sfield, which maps to the following argument,world, causing string world to be left-space-padded to a length of 42; since world has a character count of 5, 37 spaces are used for padding.
To make the example work as intended, use printf '%s%*s%s\n' hello 42 '' world - note the empty string argument following 42, which ensures that the entire field is made up of padding, i.e., spaces (you'd get the same effect if no arguments followed 42).
With printf's -v option, we can store any string formatted by printf into a variable; here we're storing $number spaces in spn. Finally, we replace all spaces by the character *, using the expansion ${spn// /*}.
Yet another possibility:
#!/bin/bash
[[ $1 = +([[:digit:]]) ]] || { printf >&2 'Argument must be a number\n'; exit 1; }
printf -v s '%*s' $((10#1))
s=${s// /*}
while [[ $s ]]; do
printf '%s\n' "$s"
s=${s%?}
done
This time we construct the variable s that contains a bunch of * (number given by user), using the previous technique. Then we have a while loop that loops while s is non empty. At each iteration we print the content of s and we remove a character with the expansion ${s%?} that removes the last character of s.
Building on gniourf_gniourf's helpful answer:
The following is simpler and performs significantly better:
#!/bin/bash
count=$1 # (... number-validation code omitted for brevity)
# Create the 1st line, composed of $count '*' chars, and store in var. $line.
printf -v line '%.s*' $(seq $count)
# Count from $count down to 1.
while (( count-- )); do
# Print a *substring* of the 1st line based on the current value of $count.
printf "%.${count}s\n" "$line"
done
printf -v line '*%.s' $(seq $count) is a trick that prints * $count times, thanks to %.s* resulting in * for each argument supplied, irrespective of the arguments' values (thanks to %.s, which effectively ignores its argument). $(seq $count) expands to $count arguments, resulting in a string composed of $count * chars. overall, which - thanks to -v line, is stored in variable $line.
printf "%.${count}s\n" "$line" prints a substring from the beginning of $line that is $count chars. long.

Increment aaa to aab using Bash?

How can I increment characters similar to how numbers are done in bash?
Example; aaa -> zzz
for i in {aaa..zzz}; do
echo -n $i;
done
Should result in:
aaa aab aac (...) zzx zzy zzz
printf '%s ' {a..z}{a..z}{a..z}
If you really want to increment a character, you have to jump through some hoops:
First, you have to get the ordinal value of the character. This can be done with the shell's weird leading-quote syntax:
$ printf -v ordA '%d' '"A'
$ echo "$ordA"
65
Next, you need to add one to that:
$ ordB=$(( 1 + ordA ))
$ echo "$ordB"
66
Then you need to format that value so it can be printfed as a character:
$ printf -v fmtB '\\x%x' "$ordB"
$ echo "$fmtB"
\x42
Then, you finally printf it:
$ printf -v chrB "$fmtB"
$ echo "$chrB"
B
Whew. I'm sure some of that can be simplified, but those're the actual steps that need to be taken.
echo {a..z}{a..z}{a..z}
would suffice.
for n in {a..z}{a..z}{a..z}; do echo -n " $n"; done
Expanding on a comment to the answer by kojiro, if you really want to know how to increment an alphabetic string (as opposed to enumerating all possibilities), here's a solution (bash only, and it depends on the shell option extglob). It only works on strictly alphabetic lower-case strings, but it should be "obvious" how to extend it:
inc () {
local pfx=${1%%[^z]*(z)};
[[ $pfx != $1 ]] && echo $pfx$(tr a-z b-za <<<${1:${#pfx}})
}
Example:
$ a=zyy; while echo $a; a=$(inc $a); do :; done
zyy
zyz
zza
zzb
zzc
zzd
zze
zzf
zzg
zzh
zzi
zzj
zzk
zzl
zzm
zzn
zzo
zzp
zzq
zzr
zzs
zzt
zzu
zzv
zzw
zzx
zzy
zzz

Variable substitution in bash printf {}

I am trying to print true 10 times using a var and its not working
count=10
printf 'true\n%.0s' {1..$count}
This works:
printf 'true\n%.0s' {1..10}
I understand that {} are evaluated before vars but I cannot get around it.
That's not a problem with printf, it's a problem with {1..$count}. That expansion can only be done with constants.
for ((i=1; i<=10; i++)); do
printf 'true\n%.0s' "$i"
done
...or, if you really want to expand onto a single command line, collect your arguments into an array first:
arr=()
for ((i=1; i<=10; i++)); do arr+=( "$i" ); done
printf 'true\n%.0s' "${arr[#]}"
To explain why: Brace expansion ({1..10}) happens before parameter expansion ($count). Thus, by the time $count is expanded to 10, no more brace expansion is going to occur.
The other way (using an external process):
printf 'true\n%.0s' $(seq $count)
For the fun of it, here's a slightly bizarre way:
mapfile -n $count a < /dev/urandom; printf 'true\n%.0s' ${!a[#]}
read http://www.cyberciti.biz/faq/unix-linux-iterate-over-a-variable-range-of-numbers-in-bash/
the way to fix this to work is:
printf 'true\n%.0s' $(eval echo "{1..$count}")

Increment variable value by 1 (shell programming)

I can't seem to be able to increase the variable value by 1. I have looked at tutorialspoint's Unix / Linux Shell Programming tutorial but it only shows how to add together two variables.
I have tried the following methods but they don't work:
i=0
$i=$i+1 # doesn't work: command not found
echo "$i"
$i='expr $i+1' # doesn't work: command not found
echo "$i"
$i++ # doesn't work*, command not found
echo "$i"
How do I increment the value of a variable by 1?
You can use an arithmetic expansion like so:
i=$((i+1))
or declare i as an integer variable and use the += operator for incrementing its value.
declare -i i=0
i+=1
or use the (( construct.
((i++))
The way to use expr:
i=0
i=`expr $i + 1`
The way to use i++ (unless you're running with -e/-o errexit):
((i++)); echo $i;
Tested in gnu bash.
you can use bc as it can also do floats
var=$(echo "1+2"|bc)
These are the methods I know:
ichramm#NOTPARALLEL ~$ i=10; echo $i;
10
ichramm#NOTPARALLEL ~$ ((i+=1)); echo $i;
11
ichramm#NOTPARALLEL ~$ ((i=i+1)); echo $i;
12
ichramm#NOTPARALLEL ~$ i=`expr $i + 1`; echo $i;
13
Note the spaces in the last example, also note that's the only one that uses $i.

Resources