This question already has answers here:
Lookup shell variables by name, indirectly [duplicate]
(5 answers)
Closed 9 years ago.
#!/bin/sh
b=( a b c d )
count=1
for a in ${b[#]}; do
example_$count=$a # how do I declare this variable "example_$count"
echo example_$count; # and how do I echo it
(( count++ ))
done
What I need is something like this:
$example_1=a
$example_2=b
$example_3=c
$example_4=d
For declaring variables, you use eval. For displaying variables, you have two solutions : eval, or Bash syntax ${!var}.
So your script becomes, with only eval :
#!/bin/bash
b=( a b c d )
count=1
for a in ${b[#]}; do
var=example_$count
eval $var=$a
eval echo \$$var
(( count++ ))
done
Or with Bash syntax for display :
#!/bin/bash
b=( a b c d )
count=1
for a in ${b[#]}; do
var=example_$count
eval $var=$a
echo ${!var}
(( count++ ))
done
The most simple solution:
example=( a b c d )
Instead of $example_1, just use ${example[0]}. At first glance, this may not look like what you want. But keep in mind that BASH is a scripting language with a history (= it has many, many quirks). So while it's not exactly what you think you need, it will work as you expect in most cases.
One reason might be that you think that arrays should start with index of 1 (which is a common beginners mistake that causes lots of problems later). But if you insist, you can insert an empty 0 element to simulate the desired behavior:
example=( "" a b c d )
of if you already have the array:
example=( "" "${example[#]}" ) # prepend empty element to existing array
Related
This question already has answers here:
Dynamic indirect Bash array
(3 answers)
Closed 3 years ago.
I'm trying to populate several files in bash. I have code that performs the logic that I want, but right now, making it run over more files means copying-and-pasting. How can I do this repeatably, in a way that scales as the number of files increases?
# first iteration: read from shuffle1.txt, write to initialpos1 and finalpos1
i=0;
while read -r a b; do
i=$(( $i + 1 ));
initialpos1[$i]=$a;
finalpos1[$i]=$b;
done < shuffle1.txt
# second iteration: read from shuffle2.txt, write to initialpos2 and finalpos2
i=0;
while read -r a b; do
i=$(( $i + 1 ));
initialpos2[$i]=$a;
finalpos2[$i]=$b;
done < shuffle2.txt
# third iteration: read from shuffle3.txt, write to initialpos3 and finalpos3
i=0;
while read -r a b; do
i=$(( $i + 1 ));
initialpos3[$i]=$a;
finalpos3[$i]=$b;
done < shuffle3.txt
If you want to encapsulate this code, consider a function:
#!/usr/bin/env bash
case $BASH_VERSION in ''|[0-3].*|4.[0-3].*) echo "ERROR: Bash 4.4+ is required" >&2; exit 1;; esac
# usage: read_columns_to_array arrayname1 arrayname2 filename
read_columns_to_array() {
local a b
declare -g -a "$1=( )" "$2=( )"
local -n __array1=$1 __array2=$2
while read -r a b; do
__array1+=( "$a" ); __array2+=( "$b" )
done <"$3"
}
for ((i=1; i<=3; i++)); do
read_columns_to_array "initialpos${i}" "finalpos${i}" "shuffle${i}.txt"
declare -p "initialpos$i" "finalpos$i" # print the arrays' current content to demonstrate
done
The $BASH_VERSION check ensures that we throw an error message when running a version of bash too old to support the necessary features -- most particularly namevars, which we later access with local -n.
funcname() { ...; } is the POSIX-standardized way to declare functions in shell languages. See https://wiki.bash-hackers.org/scripting/obsolete comparing it to some other, less-portable approaches.
local prevents our variables from leaving the scope of the function in which they're defined; unless this is done, variables are global by default. local -n is more special, creating a namevar -- a shell variable that acts as an alias to a different variable name. Here, we use __array1 and __array2 as the alias names, to avoid any likelihood of conflict with actual names we may be passed on invocation.
declare -g declares a global variable, even when run inside a function; -a makes that variable an array. Thus, our use of declare -g -a defines an array that outlives the end of the function in which it's defined.
array+=( "first new value" "second new value" ) is modern syntax for appending to an existing array, which unlike the array[$index]=value form, doesn't require the caller to maintain a counter themselves.
Note above that we're using "$i" to substitute in a numeric value -- right now, that's in a range from 1 to 3, but it could just as easily be 1 to 50.
This question already has answers here:
Bash command line and input limit
(4 answers)
Closed 4 years ago.
In a Bash script I am using a simple for loop, that looks like:
for i in $(seq 1 1 500); do
echo $i
done
This for loop works fine. However, when I would like to use a sequence of larger numbers (e.g. 10^8 to 10^12), the loop won't seem to start.
for i in $(seq 100000000 1 1000000000000); do
echo $i
done
I cannot imagine, that these numbers are too large to handle. So my question: am I mistaken? Or might there be another problem?
The problem is that $(seq ...) is expanded into a list of words before the loop is executed. So your initial command is something like:
for i in 100000000 100000001 100000002 # all the way up to 1000000000000!
The result is much too long, which is what causes the error.
One possible solution would be to use a different style of loop:
for (( i = 100000000; i <= 1000000000000; i++ )) do
echo "$i"
done
This "C-style" construct uses a termination condition, rather than iterating over a literal list of words.
Portable style, for POSIX shells:
i=100000000
while [ $i -le 1000000000000 ]; do
echo "$i"
i=$(( i + 1 ))
done
This question already has answers here:
Arithmetic expressions in Bash?
(5 answers)
Closed 6 years ago.
I am doing a school assignment in bash and got this code:
if a < 0
a = a/b
else
a = b/a
fi
The assignment says that we need to divide two number read from the keyboard, and check if the first number is larger than the number 0.
echo "Write two numbers, with a space, that need to be divided:"
read a b
if a > 0
a = $a / $b
else
a = $b / $a
fi
echo "$a"
What am I doing wrong here?
Creating a math context in bash uses (( )). Note that bash only supports integer math natively -- be sure you aren't expecting fractional output (or using fractional inputs), and see BashFAQ #22 if this limitation is relevant to you.
if (( a > 0 )); then
a=$(( a / b ))
else
a=$(( b / a ))
fi
This is my first time using Bash, so bear with me. I'm using Git Bash in Windows for a college project, trying to rewrite some C code that provides an alternate way of multiplying two numbers "a" and "b" to produce "c". This is what I came up with:
#!/bin/bash
declare -i a
declare -i b
declare -i c=0
declare -i i=0 # not sure if i have to initialize this as 0?
echo "Please enter a number: "
read a
echo "Please enter a number: "
read b
for i in {1..b}
do
let "c += a"
done
echo "$a x $b = $c"
I think part of the problem is in the for loop, that it only executes once. This is my first time using Bash, and if anyone could find the fault in my knowledge, that would be all I need.
There are problems with your loop:
You can't use {1..b}. Even if you had {1..$b} it wouldn't work because you would need an eval. It's easiest to use the seq command instead.
Your let syntax is incorrect.
Try this:
for i in $(seq 1 $b)
do
let c+=$a
done
Also, it's not necessary to declare or initialise i.
for i in {1..b}
won't work, because 'b' isn't interpreted as a variable but a character to iterate to.
For instance {a..c} expands to a b c.
To make the brace expansion work:
for i in $(eval echo "{1..$b}")
The let "c += a" won't work either.
let c+=$a might work, but I like ((c+=a)) better.
Another way is this:
for ((i = 1; i <= b; i++))
do
((c += a))
done
(might need to put #!/bin/bash at the top of your script, because sh does less than bash.)
Of course, bash already has multiplication, but I guess you knew that ...
If the absence of "seq" is your issue, you can replace it with something more portable, like
c=0
# Print an endless sequence of lines
yes |
# Only take the first $b lines
head -n "$b" |
# Add line number as prefix for each line
nl |
# Read the numbers into i, and the rest of the line into a dummy variable
while read i dummy; do
# Update the value of c: add line number
c=`expr "$c" + "$i"`
echo "$c"
done |
# Read the last number from the while loop
tail -n 1
This should be portable to any Bourne-compatible shell. The while ... echo ... done | tail -n 1 trick is necessary only if the value of c is not exported outside the while loop, as is the case in some, but not all, Bource shells.
You can implement a seq replacement with a Perl one-liner, but then you might as well write all of this in Perl (or awk, or Python, or what have you).
#!/bin/bash
i=1
until [ $i -gt 6 ]
do
echo "Welcome $i times."
i=$(( i+1 ))
done
Why we use double () in i=$(( i+1 )),and
why if we change the program to
i=$( i+1 )
or
i++
or
$i=$i+1
, it is not correct?
$( foo ) tries to execute foo as a command in a subshell and returns the result as a string. Since i+1 is not a valid shell command, this does not work.
$(( foo )) evaluates foo as an arithmetic expression.
It's just two similar (but different) syntaxes that do different things.
http://www.linuxtopia.org/online_books/advanced_bash_scripting_guide/dblparens.html
Similar to the let command, the
((...)) construct permits arithmetic
expansion and evaluation. In its
simplest form, a=$(( 5 + 3 )) would
set "a" to "5 + 3", or 8. However,
this double parentheses construct is
also a mechanism for allowing C-type
manipulation of variables in Bash.