bash get list length when list name is not fixed - bash

Suppose I have two lists:
lista="a b c d"
listb="e f"
I would like to write a function that returns the number of items on a given list:
>>foo $lista
4
>>foo $listb
2
I've tried using ${#<varname>[#]} syntax, also ${#!<varname>[#]}, unsuccessfully.
Thanks

You can use wc -w for this:
$ lista="a b c d"
$ wc -w <<< "$lista"
4
$ listb="e f"
$ wc -w <<< "$listb"
2
From man wc:
-w, --words
print the word counts
To make it function, use:
list_length () {
echo $(wc -w <<< "$#")
}
And then you can call it like:
list_length "a b c"

Put them into BASH array and look at array length.
a=($lista)
echo ${#a[#]}
4
a=($listb)
echo ${#a[#]}
2

If you indeed want to write a function, you can take advantage of normal parameter parsing and the fact that $# contains the number of parameters passed:
foo() { echo $#; }

Related

What is the simplest way to get the length of a range or set of items in bash?

I have a situation where I might specify a for loop range using either
a range
"{1..10}"
e.g. length = 10
or a set of items
"tcp udp"
e.g. length = 2
What is the simplest way to get the length of that set?
In both cases, create an array:
$ x=({1..10})
$ y=(tcp udp)
then use parameter expansion to get the number of elements in the array.
$ echo "${#x[#]}"
10
$ echo "${#y[#]}"
2
# Bash, as brace expansion are not POSIX
count=0
for _ in {1..10}; do ((count++)); done
echo "$count" # 10
# POSIX compliant
count=0
for _ in tcp udp; do : $((count=count+1)); done
echo "$count" # 2
But neither of these things makes sense, as if you know the values beforehand then you would also know the count.
If you have an array in bash, then you can simply read the length:
arr=({1..10})
echo "${#arr[#]}" # 10
arr=(tcp udp)
echo "${#arr[#]}" # 2
Or if you have a file, or a list of matches from eg. grep then either using wc -l or grep -c would work:
$ my_cmd | wc -l
$ my_cmd | grep -c 'pattern'

In bash how to use the last argument- and adding all other arguments to array

I have a script where the user can add as many arguments as he would like (numbers).
The script will sum all the numbers beside the last number - The last number (argument) is the number that I need to divide by
For example:
./test.sh 2 2 6 5
This will sum the first 3 numbers (2+2+6) and divide the answer by 5 (the last argument)
How can I use the last argument? Echo ????
How can I move loop the first arguments besides the last one – I would like that all 3 arguments will be added to an array and I can loop it
Please note that the number of arguments can be changed
How can I use the last argument? Echo ????
Granting $# > 0, you can use "${!#}".
How can I move loop the first arguments besides the last one – I would
like that all 3 arguments will be added to an array and I can loop it
Again granting $# > 0, you can refer to "${#:1:$# - 1}".
Read the Arrays section in the bash manual to know how to properly expand arrays.
I also recommend learning how quoting works and knowing the dangers of unwanted word splitting and globbing.
Shortly (with bashisms)
As this question is tagged integer-arithmetic and bash:
Here is a small and efficient script:
#!/bin/bash
addVals=${*: 1 : $# - 1}
declare -i intResult=" ( ${addVals// /+} ) / ${#: -1} "
echo $intResult
But there's no loop...
Long answer
How can I use the last argument? Echo ????
You could make your tries in command line:
set -- 2 2 6 5
Then
echo $#
2 2 6 5
echo ${*: 3}
6 5
echo ${*: -1}
5
echo ${*: 1 : -1}
bash: -1: substring expression < 0
echo $#
4
echo ${*: 1 : $# -1}
2 2 6
Ok, then
someVar=${*: 1 : $# -1}
echo ${someVar// /:SpaceReplacment:}
2:SpaceReplacment:2:SpaceReplacment:6
so
declare -i result
result=" ( ${someVar// /+} ) / ${*: -1} "
echo $result
2
How can I move loop the first arguments besides the last one – I would like that all 3 arguments will be added to an array and I can loop it
Still forward, under command line...
someArray=("${#: 1: $# -1 }")
Use declare -p to show $someArray's content:
declare -p someArray
declare -a someArray=([0]="2" [1]="2" [2]="6")
Then
declare -i mySum=0
for i in "${someArray[#]}";do
mySum+=i
done
echo $mySum
10
echo $(( mySum / ${*: -1} ))
2
Please note that the number of arguments can be changed
Please note:
Using double quotes allow processing of strings containing spaces:
set -- foo bar 'foo bar baz'
echo ${2}
bar
echo ${*: $# }
foo bar baz
Difference betweeen use of "$#" (array to array) and "$*" (array to string)
set -- foo bar 'foo bar' 'foo bar baz'
If I take 3 first elements:
someArray=("${#: 1: $# -1 }")
declare -p someArray
declare -a someArray=([0]="foo" [1]="bar" [2]="foo bar")
But
someArray=("${*: 1: $# -1 }")
declare -p someArray
declare -a someArray=([0]="foo bar foo bar")
There are about a thousand ways of doing this. As you would like to make use of integer arithmetic, you can do the following in bash
A short semi-cryptic version would be:
IFS=+
echo $(( ( ${*} - ${#:-1} ) / ${#:-1} ))
Here we make use of the difference between "${*}" and "${#}" to perform the sum by setting IFS=+ (See What is the difference between "$#" and "$*" in Bash?)
A long classic approach would be:
for i in "$#"; do ((s+=i)); done
echo $(( (s-${#:-1})/${#:-1} ))
It's easier to sum all terms and subtract the last term afterwards

Bash script to add absolute values of numbers seperated by spaces

I need a bash script to find the sum of the absolute value of integers separated by spaces. For instance, if the input is:
1 2 -3
the script should print 6 to standard output
I have:
while read x ; do echo $(( ${x// /+} )) ; done
which gives me
0
Without over complicated things, how would I include an absolute value of each x in that statement so the output would be:
6
With Barmar's idea:
echo "1 2 -3" | tr -d - | tr ' ' '+' | bc -l
Output:
6
You have almost done it, but the -s must have been removed from the line read:
while read x; do x=${x//-}; echo $(( ${x// /+} )); done
POSIX friendly implementation without running a loop and without spawning a sub-shell:
#!/usr/bin/env sh
abssum() {
IFS='-'
set -- $*
IFS=' '
set -- $*
IFS=+
printf %d\\n $(($*))
}
abssum 1 2 -3
Result:
6

Can I output multiple value in bash, like read A B C <<< "1 2 3"?

I am learning bash shell.
I found a command "read" which can deliver multiple values to different variables, like
read A B C ... <<< "1 2 3 ..."
Now I make a function
function echo_multiple_values() {
echo "1 2 3 ..."
}
Do I have a smart way to output multiple values from a function, like
A B C ...=$(echo_multiple_values)
Thank you very much.
You can't do
A B C ...=$(echo_multiple_values)
but you may wish to do something like below :
myfun(){
arr=( {1..3} ) #{a..b} is a bash range
echo "${arr[#]}"
}
read a b c <<<"$(myfun)"
Also, you could do something like below
$alphabets=( {a..z} )
$ nums=( {1..26} )
$ read "${alphabets[#]}" <<<"${nums[#]}"
$ echo $a
1
$ echo $c
3
$ echo $z
26

Bash/Awk - Store in a variable part of one argument

For a given script I can supply one argument that has the following form:
-u[number][letter][...]
Examples: -u2T34T120F -u1T2T10F
Letters are either T or F, and the number is an integer number, which can be up to 999.
I would like a write loop where in each iteration the number is stored in variable "a" and the corresponding letter in variable "b". The loop goes through all the number-letter pairs in the argument.
For the first example, the argument is -u2T34T120F the iterations would be:
First: a=2 b=T
Second: a=34 b=T
Third: a=120 b=F
End of loop
Any suggestion is most welcome.
Here's one way to do it with GNU awk:
<<<"2T34T120F" \
awk -v RS='[TF]' 'NF { printf "a: %3d b: %s\n", $0, RT }'
Output:
a: 2 b: T
a: 34 b: T
a: 120 b: F
To use this in a bash while-loop do something like this:
<<<"2T34T120F" \
awk 'NF { print $0, RT }' RS='[TF]' |
while read a b; do
echo Do something with $a and $b
done
Output:
Do something with 2 and T
Do something with 34 and T
Do something with 120 and F
$ var='-u2T34T120F'
$ a=($(grep -o '[0-9]*' <<< "$var"))
$ b=($(grep -o '[TF]' <<< "$var"))
$ echo ${a[0]} ${a[1]} ${a[2]}
2 34 120
$ echo ${b[0]} ${b[1]} ${b[2]}
T T F
how about this:
kent$ while IFS=' ' read a b; do echo "we have a:$a,b:$b\n---"; done<<< $(echo '-u2T34T120F'|sed 's/^-u//;s/[TF]/ &\n/g')
we have a:2,b:T
---
we have a:34,b:T
---
we have a:120,b:F
---
clear version:
while IFS=' ' read a b
do
echo "we have a:$a,b:$b\n---";
done<<< $(echo '-u2T34T120F'|sed 's/^-u//;s/[TF]/ &\n/g')
You can use parameter expansion in bash:
#! /bin/bash
set -- -u2T34T120F # Set the $1.
string=${1#-u} # Remove "-u".
while [[ $string ]] ; do
a=${string%%[FT]*} # Everything before the first F or T.
string=${string#$a} # Remove the $a from the beginning of the string.
b=${string%%[0-9]*} # Everything before the first number.
string=${string#$b} # Remove the $b from the beginning of the string.
echo $a $b
done
Or, using the same technique, but with arrays:
a=(${string//[TF]/ }) # Remove letters.
b=(${string//[0-9]/ }) # Remove numbers.
for (( i=0; i<${#a[#]}; i++ )) ; do
echo ${a[i]} ${b[i]}
done

Resources