How to split each element of an array in zsh? - zsh

If I have an array:
x=( a:1 b:2 c:3 )
How can I split each element of the array on the colon? I'm trying to do this in order to form an associative array.
I've tried several variations on this, but none seem to work:
print -l ${(s,:,)x}
Is this possible? If so, what am I missing?

Ok - further thinking got me to this solution, that might probably be applicable to the problem behind the question asked:
Loop through the array x!
> x=(a:1 b:2 c:3)
> typeset -A z
> for i in $x; do z+=(${(s,:,)i}); done
> echo $z
1 2 3
> echo ${z[a]}
1
I hope that's more helpful than the first answer(s) :-)
Since x is an array, you shouldn't forget the [#], i.e. print ${(s,:,)x[#]}; thus your code becomes print -l ${(s,:,)x[#]}; but the solution is still another step:
> typeset -A z
> x="a:1 b:2 c:3"
> z=($(echo ${(s,:,)x}))
> echo $z[a]
1
The assignement to the associative array is done with the output of the echo statement.
To clarify further and include your original example x:
> typeset -A z
> x=(a:1 b:2 c:3)
> z=($(echo ${(s,:,)x[#]}))
> echo ${z[a]}
1
> echo ${z[b]}
2
(edit: switched thoughts midwriting :-D, should make more sense now)
(edit 2: striked brainf*rt - similarities between zsh and bash aren't as broad as I assumed)

typeset -A x will make x an associative array
$ typeset -A x
$ x=( a 1 b 2 c 3 )
$ echo $x[a]
1
$ echo $x[c]
3
$ x[d]=5
$ echo $x[d]
5
But i don't know how to split on the colon if you need to start from the string "a:1 b:2 c:3".

Related

Use output number of echo as input for seq

So as an input, i get echo [number]. For example: echo 5.
I need to get as output the sequence of numbers from 1 to [number].
So if I get 5 as input, I need: 1 2 3 4 5 (all on a separate line).
I know I can use seq 5 to get 1 2 3 4 5 but the issue is that I need to use pipes.
So the final command should be like this: echo 5 | seq [number] which should give 1 2 3 4 5 as output. My issue is that I don't know how to get the output from echo as my input for seq.
Assuming that echo 5 is an example replacement of an unknown program that will write a single number to stdout and that this output should be used as an argument for seq, you could use a script like this:
file seqstdin:
#!/bin/sh
read num
seq "$num"
You can use it like
echo 5 | ./seqstdin
to get the output
1
2
3
4
5
You can also write everything in a single line, e.g.
echo '5'| { read num; seq "$num"; }
Notes:
This code does not contain any error handling. It uses the first line of input as an argument for seq. If seq does not accept this value it will print an error message.
I did not use read -r or IFS= because I expect the input to be a number suitable for seq. With other input you might get unexpected results.
You can use the output of the echo command as follows:
seq $(echo 5)
In case you're dealing with a variable, you might do:
var=5
echo $var
seq $var

Using "declare" in Zsh

I am following this thread: https://stackoverflow.com/a/19742842/5057251
for typeset (or declare) in ZSH, not BASH.
#Declare (or typeset) an array of integers
#declare -ai int_array
typeset -ai int_array
int_array=(1 2 3)
echo "${int_array[#]}"
Then
# Attempt to change 1st element to string. (expect to fail)
int_array[1]="Should fail" || echo "error: ${LINENO}"
echo "${int_array[#]}"
Bash finds the error, gracefully reports error and lineno, prints:
1 2 3
But Zsh accepts, prints:
Should fail 2 3
Not sure why different.
There are two problems here:
In bash, and zsh, assigning a string to an integer variable causes that string to be evaluated as an arithmetic expression. Thus, this is not an error:
$ typeset -i foo
$ foo="bar"
If bar was a variable previously set to an arithmetic expression, then bar's expansion would be evaluated as such:
$ bar=10+2
$ typeset -i foo
$ foo="bar"
$ echo "$foo"
12
The error in your assignment, of course, is that there's no way to expand Should fail like that. If it were, say, Should - fail (an arithmetic expression subtracting the value of the two variables Should and fail, for example, it would still work:
$ foo="Should - fail"
$ echo "$foo"
0
The second problem is that nothing in the zsh docs indicate that -i may be set for an entire array, and so the -a in -ai is ignored:
bash-5.0$ typeset -ai foo
bash-5.0$ declare -p foo
declare -ai foo=([0]="0") # the previous value was retained in the array
vs zsh:
% typeset -ai foo
% foo[1]=10
% foo[2]=20
% declare -p foo
typeset -i foo=20 # treated as a normal variable, not array
What you're seeing is essentially int_array being redeclared as an array (without any qualifiers) when you do int_array=(1 2 3):
% foo=(1 2 3)
% declare -p foo
typeset -a foo=( 1 2 3 )
Using zsh typeset can produce a few possible outcomes:
- no errors, works (yeah!).
- errors, script fails (fix!).
- no errors, but unexpected behavior. (scratch head)
As an example of last category, this produces no errors, but the typeset -p reveals -i is ignored.
{
unset int_array
typeset -ia int_array
int_array=(1 2 3)
echo $? "-Point A"
typeset -p int_array
} always {
echo $? "-Point B"
typeset -p int_array
(( TRY_BLOCK_ERROR=0 ))
}
echo $? "-Point C"
echo "survived"
produces
0 -Point A
typeset -a int_array=( 1 2 3 )
0 -Point B
typeset -a int_array=( 1 2 3 )
0 -Point C
survived
The first line unsets int_array. The typeset command declares
int_array to be both an array and int, which is not what zsh allows. The next
line assigns int_array to a value. There is no error as the $? tells us,
but close examination of final typeset -p int_array reveals what actually
happened.
With a small change, we can produce errors and use the always block and
typeset -p to find more details.
{
unset int_array
typeset -ia int_array=(1 2 3) # error
echo $? "-Point A"
typeset -p int_array
} always {
echo $? "-Point B"
typeset -p int_array
(( TRY_BLOCK_ERROR=0 ))
}
echo $? "-Point C"
echo "survived"
040_declare_version2.sh:typeset:135: int_array: inconsistent type for assignment
1 -Point B
040_declare_version2.sh:typeset:140: no such variable: int_array
1 -Point C
survived
The only difference is int_array was given a value in the faulty typeset -ia statement.
This produces errors, and the script jumps to the always block.
The (( TRY_BLOCK_ERROR=0)) allows the script to continue
and not terminate, but the error is still reported at "Point C".
To check shell version:
$SHELL --version
zsh 5.4.2 (x86_64-ubuntu-linux-gnu)

bc arithmetic Error

i am trying to solve this bash script which reads an arithmetic expression from user and echoes it to the output screen with round up of 3 decimal places in the end.
sample input
5+50*3/20 + (19*2)/7
sample output
17.929
my code is
read x
echo "scale = 3; $x" | bc -l
when there is an input of
5+50*3/20 + (19*2)/7
**my output is **
17.928
which the machine wants it to be
17.929
and due to this i get the solution wrong. any idea ?
The key here is to be sure to use printf with the formatting spec of "%.3f" and printf will take care of doing the rounding as you wish, as long as "scale=4" for bc.
Here's a script that works:
echo -e "please enter math to calculate: \c"
read x
printf "%.3f\n" $(echo "scale=4;$x" | bc -l)
You can get an understanding of what is going on with the above solution, if you run this command at the commandline: echo "scale=4;5+50*3/20 + (19*2)/7" | bc the result will be 17.9285. When that result is provided to printf as an argument, the function takes into account the fourth decimal place and rounds up the value so that the formatted result displays with precisely three decimal places and with a value of 17.929.
Alternatively, this works, too without a pipe by redirecting the here document as input for bc, as follows which avoids creating a sub-shell:
echo -e "please enter math to calculate: \c"
read x
printf "%.3f\n" $(bc -l <<< "scale=4;$x")
You are not rounding the number, you are truncating it.
$ echo "5+50*3/20 + (19*2)/7" | bc -l
17.92857142857142857142
$ echo "scale = 3; 5+50*3/20 + (19*2)/7" | bc -l
17.928
The only way I know to round a number is using awk:
$ awk 'BEGIN { rounded = sprintf("%.3f", 5+50*3/20 + (19*2)/7); print rounded }'
17.929
So, in you example:
read x
awk 'BEGIN { rounded = sprintf("%.3f", $x; print rounded }'
I entirely agree with jherran that you are not rounding the number, you are truncating it. I would go on to say that scale is probably just not behaving at all the way you want it, possibly in a way that noone would want it to behave.
> x="5+50*3/20 + (19*2)/7"
> echo "$x" | bc -l
17.92857142857142857142
> echo "scale = 3; $x" | bc -l
17.928
Furthermore, because of the behaviour of scale, you are rounding each multiplication/division separately from the additions. Let me prove my point with some examples :
> echo "scale=0; 5/2" | bc -l
2
> echo "scale=0; 5/2 + 7/2" | bc -l
5
> echo "5/2 + 7/2" | bc -l
6.00000000000000000000
However scale without any operation doesn't work either. There is an ugly work-around :
> echo "scale=0; 5.5" | bc -l
5.5
> echo "scale=0; 5.5/1" | bc -l
5
So tow things come out of this.
If you want to use bc's scale, do it only for the final result already computed, and even then, beware.
Remember that rounding is the same as truncating a number + half of the desired precision.
Let us take the example of rounding to the nearest integer, if you add .5 to a number that should be rounded up, its integer part will take the next integer value and truncation will give the desired result. If that number should have been rounded down, then adding .5 will not change its integer value and truncation will yield the same result as when nothing was added.
Thus my solution follows :
> y=$(echo "$x" | bc -l)
> echo "scale=3; ($y+0.0005)/1" | bc -l # scale doesn't apply to the +, so we get the expected result
17.929
Again, note that the following doesn't work (as explained above), thus breaking it up in two operations is really needed :
> echo "scale=3; ($x+0.0005)/1" | bc -l
17.928

Command line tool to listen on several FIFOs at once

I am looking for a tool to read several FIFOs at once (probably using select(2)) and output what is read, closing the stream when all the FIFOs are closed. To be more precise, the program would behave as follows:
$ mkfifo a b
$ program a b > c &
$ echo 'A' > a
$ echo 'B' > b
[1] + done program a b > c
$ cat c
A
B
$ program a b > c &
$ echo 'B' > b
$ echo 'A' > a
[1] + done program a b > c
$ cat c
B
A
My first attempt was to use cat, but the second example will not work (echo 'B' > b will hang), because cat reads from each argument in order, not simultaneously. What is the correct tool to use in this case?
tail will do that.
Use:
tail -q -n +1 a b
Edit: Sorry that didn't work. I'll see if I can find something else.
Sorry, I could not find anything.
If you don't want to program this yourself then my suggestion is multiple commands:
#!/bin/sh
rm c
cat a >> c &
cat b >> c &
wait
You may get some interleaving but otherwise everything should work fine. The wait is to prevent the program from exiting till all the cat programs are done (just in case you have some command you need to run after everything is done). And the rm is to make sure c starts empty since the cat commands append to the file.

echo value inside a variable?

x=102 y=x
means when i echo $y it gives x
echo $y
x --and not 102
and when i echo $x it give 102
lets say I dnt know what is inside y
and i want the value of x to be echoed with using y someting like this
a=`echo $(echo $y)`
echo $a
Ans 102
You need to tell the shell to evaluate your command twice -- once to turn $y into x, and again to get the value of $x. The most portable way I know to do this is with eval:
$ /bin/sh
$ x=100
$ y=x
$ echo $y
x
$ eval echo \$$y
100
$
(You need to escape the first $ in the eval line because otherwise the first evaluation will replace "$$" with the current pid)
If you're only concerned with bash, KennyTM's method is probably best.
In ksh 93 (I don't know whether this works in ksh 88):
$ x=102; typeset -n y=x
$ echo $x
102
$ echo $y
102
$ echo ${!y}
x
Confusingly, the last two commands do the opposite of what they do in Bash (which doesn't need to flag the variable using typeset).

Resources