How to create an array with leading zeros in Bash? - arrays

Not very difficult:
#!/bin/bash
hr=(00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23)
for i in ${hr[#]}; do
echo ${hr[i]}
done
But:
user#userver:$ ./stat.sh
00
01
02
03
04
05
06
07
./stat.sh: string 7: 08: value too great for base (error token is "08")
Bash thinks that leading zero means octal number system. What to do?

I think your fundamental problem is that you're using each element of the array to index the array itself.
If you want to just print out the array elements, you should be outputting ${i} rather than ${hr[i]}. The latter is useless in your current code since element zero is 00, element one is 01, and so on.
On the chance that this is a simplified example and you do want to reference a different array based on the content of this one, you have a couple of options:
Realise that the value of an integer and the presentation of it are two distinct things. In other words, use 0, 1, 2, ... but something like printf "%02d" $i if you need to output it (noting this is only ouputting the value in the first array, not the one you're looking up things in).
Exclusively use strings and a string-based associative array rather than an integer-based one, see typeset -A for detail.

Use brace expansion (ranges, repetition).
To create an array:
hours=({00..23})
Loop through it:
for i in "${hours[#]}"; do
echo "$i"
done
Or loop through a brace expansion:
for i in {00..23}; do
echo "$i"
done
Regarding your error, it's because in bash arithmetic, all numbers with leading zeroes are treated as octals, and 08 and 09 are invalid octal numbers. All indexed array subscripts are evaluated as arithmetic expressions. You can fix the problem by using the notation base#number to specify a number system. So for base 10: 10#09, or for i=09, 10#$i. The variable must be prefixed with $, 10#i does not work.
You should be printing your array like this anyway:
Loop through elements:
for i in "${hr[#]}"; do
echo "$i"
done
Loop through indexes:
for i in "${!hr[#]}"; do
echo "index is $i"
echo "element is ${hr[i]}"
done
If you need to do arithmetic on the hours, or any zero padded number, you will lose the zero padding. You can print it again with printf: printf %.2d "$num", where 2 is the minimum width.

When you iterate with:
for i in ${hr[#]}; do
It is iterating the values of the array witch are:
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
But when within the loop it has:
echo ${hr[i]}
it is using i as the index of the hr array.
In Bash, an index within the brackets of an array like [i] is an arithmetic context. It means while the value of i=08 the leading 0 within the arithmetic context causes the number to be treated as an octal number, and 8 is an invalid octal number.
If you wanted to iterate your array indexes to process its values by index, then you'd start the loop as:
for i in "${!hr[#]}"; do
This one will perfectly work as it iterates the index into the variable i :
#!/usr/bin/env bash
declare -a hr=(00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23)
for i in "${!hr[#]}"; do
printf '%s\n' "${hr[i]}"
done
Now if all you want is iterate the values of the hr array, just do this way:
#!/usr/bin/env bash
declare -a hr=(00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23)
for e in "${hr[#]}"; do
printf '%s\n' "$e"
done
No need to index the array within the loop, since the elements are already expanded into e.

You can specify it's 10-based :
echo ${hr[10#$i]}

Related

Two arrays with different length in a loop

I have two arrays with different length, and I need to use them in the same loop.
This is the code
#!/bin/bash
data=`date +%Y-%m-%d`
data1=`date -d "1 day" +%Y-%m-%d`
cd /home/test/em_real/
#first array (today and tomorrow)
days="$data $data1"
#second array (00 till 23)
hours="00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23"
for value in $hours
do
cp /home/test/em_real/mps_"${days[i++]}"_"$value":00:00 /home/DOMAINS/test_case/
sleep 10
done
Tt fails, doesn't get days.
How can I do it?
#fedorqui If now, inside the bucle, I want to remove the dash (-) of days and do another order, I don't know why it doesn't get the string , the code is the following:
days=("$data" "$data1") #create an array properly
for value in {00..23}; do
for day in "${days[#]}"; do
cp "/path/mps_${day}_${value}:00:00" /another/path/test_case/
d=d01
hourSIMULATION=01
clean= echo ${day} | sed -e 's/-//g'
sed -e 's/<domine>/'$d'/g' -e 's/<data-initial>/'$clean$value'/g' -e 's/<hour-SIMULATION>/'$hourSIMULATION'/g' run_prhours > run_pr
done
done
The string $dayclean is empty when I check inside run_pr, do you know what could be the reason?
You are using days[i++] but no i is defined anywhere. Not sure what you want to do with ${days[i++]} but $days is just a string containing "$data $data1".
You probably want to say days=($data $data1) to create an array.
Also, you can say for hour in {00.23} instead of being explicit on the numbers.
Then, you want to loop through the hours and then through the days. For this, use a nested loop:
days=("$data" "$data1") #create an array properly
for value in {00..23}; do
for day in "${days[#]}"; do
cp "/path/mps_${day}_${value}:00:00" /another/path/test_case/
done
done

Bash parse dynamic arrays and store specific values in an other

I am sending a dbus-send command which returns something like :
method return sender=:1.833 -> dest=:1.840 reply_serial=2
array of bytes [
00 01 02 03 04 05
]
int 1
boolean true
The "array of bytes" size is dynamic an can contains n values.
I store the result of the dbus-send command in an array by using :
array=($(dbus-send --session --print-repl ..readValue))
I want to be able to retrieve the values contained into the array of bytes and be able to display one or all of them if necessary like this :
data read => 00 01 02 03 04 05
or
first data read => 00
First data is always reachable by {array[10]} and I think is it possible to use a structure like :
IFS=" " read -a array
for element in "${array[#]:10}"
do
...
done
Any thoughts on how to accomplish this?
You really should use some library for dbus, like Net::DBus or something similar.
Anyway, for the above example you could write:
#fake dbus-send command
dbus-send() {
cat <<EOF
method return sender=:1.833 -> dest=:1.840 reply_serial=2
array of bytes [
00 01 02 03 04 05
]
int 1
boolean true
EOF
}
array=($(dbus-send --session --print-repl ..readValue))
data=($(echo "${array[#]}" | grep -oP 'array\s*of\s*bytes\s*\[\s*\K[^]]*(?=\])'))
echo "ALL data ==${data[#]}=="
echo "First item: ${data[0]}"
echo "All items as lines"
printf "%s\n" "${data[#]}"
data=($(echo "${array[#]}" | sed 's/.*array of bytes \[\([^]]*\)\].*/\1/'))
echo "ALL data ==${data[#]}=="
echo "First item: ${data[0]}"
echo "All items as lines"
printf "%s\n" "${data[#]}"
for the both example prints
ALL data ==00 01 02 03 04 05==
First item: 00
All items as lines
00
01
02
03
04
05

Efficiently iterating successive elements of a transposed matrix (via bit operators)

Let's consider matrices which are internally represented as a 1 dimensional array.
For instance a matrix(3, 4) is really an array (of say type double) or 3*4 elements. Here is the 'memory layout' of the matrix:
00 01 02 03
04 05 06 07
08 09 10 11
As such it's very easy to iterate (row by row, left to right) over all the elements of the matrix: it's just an 32-bit integer going from 0 to 11. This is what the transpose looks like:
00 04 08
01 05 09
02 06 10
03 07 11
What is a (fast) algorithm that taking as input a single 32-bit integer representing the i-th element of the transposed matrix (row by row, left to right) returns the index corresponding to the internal representation? By single I mean that an 'incremental' algorithm is not what I'm looking for, the function just take as input a single 32-bit integer (plus number of rows and columns) and output a single 32-bit integer. I mentioned bit-wise operators as it's likely to be the fastest way to solve the problem but any efficient solution suffice really.
In the example above:
0 --> 0
1 --> 4
2 --> 8
3 --> 1
4 --> 5
5 --> 9
6 --> 2
...
Also, what restrictions (if any) need to be imposed on the number of rows and columns (we already have that num_row*num_col fits in a 32-bit integer) so that the algorithms is guaranteed to work.
Thank you!
As long as the dimensions remain small, you can use a constant as a lookup table:
0x4cd0b73a62951840 >> (x*4)) & 15
If they get slightly larger, you could split this into e.g. generating the upper and lower bits of the result:
((0x00fea540 >> (x*2)) & 3) | (((0x00924924 >> (x*2) & 3) << 2))
Eventually though, the straight-forward approach will be faster.

shell programming: define array including zero-padded values

I just started using shell programming. I want to automatically change directories and then rename some files in there. Here's my problem: The name of the directories are numbered but directories < 10 are zero-padded (01 02...09). How can I define an array using some sort of sequencing without typing each directory name manually?
This is what I've tried so far:
array = (printf "%.2d " {1..8} {11..27} {29..32} {34..50}) ## should say 01 02 03 ..08 11..27 29..32 34..50
for i in "${array[#]}"
do
echo "dir_a/dir_b/sub$i/dir_c/"
done
However, it doesn't work and the result looks like: "subprintf", "sub%.2s", "sub1" etc.
Can you help me there?
In a next step I want to filter certain numbers in the array, e.g. 03, 09, 10, 28, 33 as these directories don't exist. Is there some easy solution to create such an array without concatenating 5 separate arrays?
Many thanks in advance,
Kati
Is there a need to use arrays? Otherwise, for bash 4, you can do
for i in {01..08} {11..27} {29..32} {34..50}; do
echo "dir_a/dir_b/sub${i}/dir_c/"
done
For an older version of bash you have to add the 0 yourself:
for i in 0{1..8} {11..27} {29..32} {34..50}; do
echo "dir_a/dir_b/sub${i}/dir_c/"
done
Of course, if you want to have an array, you can do
array=({01..08} {11..27} {29..32} {34..50})
or
array=(0{1..8} {11..27} {29..32} {34..50})
You could do this:
declare -a dirs=('01' '02' '03' '04' '05' '06' '07' '08')
echo ${dirs[#]}
01 02 03 04 05 06 07 08
# Make up next sequence
declare -a b=`seq 11 18`
echo ${b[#]}
11 12 13 14 15 16 17 18
# Add sequences together
dirs=("${dirs[#]}" ${b})
echo ${dirs[#]}
01 02 03 04 05 06 07 08 11 12 13 14 15 16 17 18
find [0-9][0-9] -type d | while read dirname
do
if [ $(echo "${dirname}" | sed -n '/01/p') ]
then
cd "${dirname}"
mv foo bar
cd ..
fi
done
Then you can just write another elif and sed check for every directory which contains files you want to rename. I know it's not what you asked for, but it is infinitely simpler. If you're allowed to, I'd also strongly recommend renaming that directory tree, as well.
#/bin/bash
raw=({01..08} {11..27} {29..32} {34..50})
filter=(03 09 10 28 33)
is_in() {
for e in "${#:2}"; do [[ "$e" == "$1" ]] && return 0; done
return 1
}
for i in ${raw[#]}; do
is_in $i ${filter[#]} || echo "dir_a/dir_b/sub$i/dir_c"
done
It'll take the numbers in the raw array and exclude every occurance of the ones in the filter array.

Is there a pattern in these bitshifts?

I have some Nikon raw files (.nef) which were rendered useless during a USB transfer. However, the size seems fine and only a handful of bits are shifted - by a value of -0x20 (hex) or -32 (dec).
Some of the files could be recovered later with another Computer from the same Card and now I am searching for a solution to recover the other >100 files, which have the same error.
Is there a regular pattern? The offsets seem to be in intervals of 0x800 (2048 in dec).
Differences between the two files
1. /_LXA9414.dump: 13.703.892 bytes
2. /_LXA9414_broken.dump: 13.703.892 bytes
Offsets: hexadec.
84C00: 23 03
13CC00: B1 91
2FA400: 72 52
370400: 25 05
4B9400: AE 8E
641400: 36 16
701400: FC DC
75B400: 27 07
925400: BE 9E
A04C00: A8 88
AC2400: 2F 0F
11 difference(s) found.
Here are more diffs from other files:
http://pastebin.com/9uB3Hx43

Resources