Store for loop results as a variable in bash - linux

I have a loop similar to the following:
for time in ${seconds_list}; do
echo "scale=2; (${cur_time}-${time})/3600" | bc
done
Of course, I could "echo" the results to a file and be done with it, but I think a more elegant approach would be to store all the for-loop results in one variable, that I could use at a later time.
The variable containing all results would have to look something like this:
var='30.25
16.15
64.40
29.80'
Is there an easy way in which I can achieve this?

It's really easy, you can just redirect the output of the whole loop to a variable (if you want to use just one variable as stated):
VARIABLE=$(for time in ...; do ...; done)
your example:
var=$(for time in ${seconds_list}; do
echo "scale=2; (${cur_time}-${time})/3600" | bc
done)
Just enclosing your code into $().

Better to use a BASH array to store your results:
results=()
for time in ${seconds_list}; do
results+=($(bc -l <<< "scale=2; ($cur_time-$time)/3600"))
done
# print the results:
printf "%s\n" "${results[#]}"

Related

Need advice for using nested command subsitution

I'm trying to wrap my head around nested command substitution. I tried nesting backticks but obviously that doesn't work. How would you nest the following without declaring the ${host} variable first?
host=$(hostname|cut -c1-14);for id in `aladmin list|grep ${host}|awk '{print $2}'`;do aladmin delete ${id};done
The command lists all alarms on a server, greps for the first 14 characters of the hostname and then deletes the alarm with the alarm ID found in field 2 by awk.
My question does in no way duplicate the 'hello' in previous post:
How to properly nest Bash backticks
Thanks in advance,
Bjoern
Do everything in awk. There's no need to use the for loop and the grep, etc. There are better ways than this, but as a first approximation, try something like:
aladmin list | awk "/$(hostname | cut -c1-14)/"'{ print "aladmin delete " $2 | "sh"}'

bash shell - increment (associative) array value

Just want to share with you something I did not easily find by myself...
I am a newbie in shell script and was just wondering how can I increment a value of a an associative array.
Let's assume this script:
#!/bin/bash
declare -A b # declare an associative array
a="aaa"
b[$a]=1
echo ${b[#]} # display all the values
echo ${b[$a]} # display the first value (1)
echo ${b[aaa]} # display the first value as well (1)
The solution can be
((b[$a]++))
echo ${b[#]} # display 2
Now that I found it, it seems evident, but I spent some time to get it...
I hope this can save some time to people :)
As describe above, the solution can be
((b[$a]++)) # or (('b[$a]'++)) for a more secure way as pointed by #gniourf_gniourf
echo ${b[#]} # display 2
Now that I found it, it seems evident, but I spent some time to get it...
I hope this can save some time to people :)

How to filter UNIX stdout by numerical constraints?

I want to take a stdout stream and filter it to only save values greater than 100 without using awk. I have a bunch of other unix commands piped together, which results in a stdout stream of various numbers. I want to pass any number greater than 99 into a file. I image something like this:
several commands | unix-tool 100 > stdout_that_is_>99
It seems like this should be trivial and that I am missing something. Thanks!
You could just use grep with a regex to match any 3+ digit number, eg:
command | grep '[1-9][0-9]\{2\}' >"stdout_that_is_>99"
This would be easily adaptable to work at any order of magnitude.
gefilt() {
while read num; do
if (( $num+0 >= $1 )); then
echo $num
fi
done
}
several commands | gefilt 100

Pipe output to bash function

I have as simple function in a bash script and I would like to pipe stdout to it as an input.
jc_hms(){
printf "$1"
}
I'd like to use it in this manner.
var=`echo "teststring" | jc_hms`
Of course I used redundant functions echo and printf to simplify the question, but you get the idea. Right now I get a "not found" error, which I assume means my parameter delimiting is wrong (the "$1" part). Any suggestions?
Originally the jc_hms function was used like this:
echo `jc_hms "teststring"` > //dev/tts/0
but I'd like to store the results in a variable for further processing first, before sending it to the serial port.
EDIT:
So to clarify, I am NOT trying to print stuff to the serial port, I'd like to interface to my bash functions should the "|" pipe character, and I am wondering if this is possible.
EDIT: Alright, here's the full function.
jc_hms(){
hr=$(($1 / 3600))
min=$(($1 / 60))
sec=$(($1 % 60))
printf "$hs:%02d:%02d" $min $sec
}
I'm using the function to form a string which come this line of code
songplaytime=`echo $songtime | awk '{print S1 }'`
printstring="`jc_hms $songplaytime`" #store resulting string in printstring
Where $songtime is a string expressed as "playtime totaltime" delimited by a space.
I wish I can just do this in one line, and pipe it after the awk
printstring=`echo $songtime | awk '{print S1 }' | jc_hms`
like so.
To answer your actual question, when a shell function is on the receiving end of a pipe, standard input is inherited by all commands in the function, but only commands that actually read form their standard input consume any data. For commands that run one after the other, later commands can only see what isn't consumed by previous commands. When two commands run in parallel, which commands see which data depends on how the OS schedules the commands.
Since printf is the first and only command in your function, standard input is effectively ignored. There are several ways around that, including using the read built-in to read standard input into a variable which can be passed to printf:
jc_hms () {
read foo
hr=$(($foo / 3600))
min=$(($foo / 60))
sec=$(($foo % 60))
printf "%d:%02d:%02d" "$hr" "$min" "$sec"
}
However, since your need for a pipeline seems to depend on your perceived need to use awk, let me suggest the following alternative:
printstring=$( jc_hms $songtime )
Since songtime consists of a space-separated pair of numbers, the shell performs word-splitting on the value of songtime, and jc_hms sees two separate parameters. This requires no change in the definition of jc_hms, and no need to pipe anything into it via standard input.
If you still have a different reason for jc_hms to read standard input, please let us know.
You can't pipe stuff directly to a bash function like that, however you can use read to pull it in instead:
jc_hms() {
while read -r data; do
printf "%s" "$data"
done
}
should be what you want
1) I know this is a pretty old post
2) I like most of the answers here
However, I found this post because I needed to something similar. While everyone agrees stdin is what needs to be used, what the answers here are missing is the actual usage of the /dev/stdin file.
Using the read builtin forces this function to be used with piped input, so it can no longer be used in a typical way. I think utilizing /dev/stdin is a superior way of solving this problem, so I wanted to add my 2 cents for completeness.
My solution:
jc_hms() {
declare -i i=${1:-$(</dev/stdin)};
declare hr=$(($i/3600)) min=$(($i/60%60)) sec=$(($i%60));
printf "%02d:%02d:%02d\n" $hr $min $sec;
}
In action:
user#hostname:pwd$ jc_hms 7800
02:10:00
user#hostname:pwd$ echo 7800 | jc_hms
02:10:00
I hope this may help someone.
Happy hacking!
Or, you can also do it in a simple way.
jc_hms() {
cat
}
Though all answers so far have disregarded the fact that this was not what OP wanted (he stated the function is simplified)
I like user.friendly's answer using the Bash built-in conditional unset substitution syntax.
Here's a slight tweak to make his answer more generic, such as for cases with an indeterminate parameter count:
function myfunc() {
declare MY_INPUT=${*:-$(</dev/stdin)}
for PARAM in $MY_INPUT; do
# do what needs to be done on each input value
done
}
Hmmmm....
songplaytime=`echo $songtime | awk '{print S1 }'`
printstring="`jc_hms $songplaytime`" #store resulting string in printstring
if you're calling awk anyway, why not use it?
printstring=`TZ=UTC gawk -vT=$songplaytime 'BEGIN{print strftime("%T",T)}'`
I'm assuming you're using Gnu's Awk, which is the best one and also free; this will work in common linux distros which aren't necessarily using the most recent gawk. The most recent versions of gawk will let you specify UTC as a third parameter to the strftime() function.
The proposed solutions require content on stdin or read to be only conditionally called. Otherwise the function will wait for content from the console and require an Enter or Ctrl+D before continuing.
A workaround is to use read with a timeout. e.g. read -t <seconds>
function test ()
{
# ...
# process any parameters
# ...
read -t 0.001 piped
if [[ "${piped:-}" ]]; then
echo $piped
fi
}
Note, -t 0 did not work for me.
You might have to use a different value for the time-out.
Too small a value might result in bugs and a too large time-out delays the script.
seems nothing works, but there are work arounds
mentioned work around xargs ref function
$ FUNCS=$(functions hi); seq 3 | xargs -I{} zsh -c "eval $FUNCS; hi {}"
then this doesn't work either because your function could reference another function. so I ended up writing some function that accepts pipe inputs, like this:
somefunc() {
while read -r data; do
printf "%s" "$data"
done
}

How to do a while loop with a string redirected into it

Im trying to loop though a string with HTTP links inside and newlines, I want to loop over a line at a time.
At the moment I have
echo -e "$HTTP_LINKS" | while read HTTP_S_LINK ; do
TEST_STRING="test"
done
But this way I don't have access to the TEST_STRING out side the loop, which is what I want.
I'm using the while loop so that it will loop though each newline in $HTTP_LINKS and not just the words in the string. (I don't want to use a for loop with IFS set to \n)
I thought maybe I could just do something like this
#!/bin/bash
while read HTTP_S_LINKS
do
TEST_STRING="test2"
done < $HTTP_LINKS
But of course this doesn't work as $HTTP_LINKS contains a string and not a link to a file.
You had the right idea with your 2nd snipit but you need to use 'Here Strings' via the <<< syntax. You cant access $TEST_STRING outside of your first snipit because the pipe creates a sub-shell; using the here-string does not. Also, make sure you quote "$HTTP_LINKS" otherwise you'll lose the newlines.
#!/bin/bash
HTTP_LINKS=$(echo -e "http://www.aaa.com\nhttp://www.bbb.com")
unset TEST_STRING;
while read url;
do
((TEST_STRING++))
done <<<"$HTTP_LINKS"
echo $TEST_STRING
Output
2
If you initialize and export the TEST_STRING variable outside the loop you should have access to it after the loop.

Resources