combine two variables in the same order in for loop unix - for-loop

It sounds simple but I can't do it in a simple way. In shell for loop, two vars
A=" 1 2 3 4"
B=" a b c d"
, try to print 1a 2b 3c 4d. Tried
A=" 1 2 3 4"
B=" a b c d"
for m in $A
for n in $B;
do echo $m$n done.
The output is
1
2
3
4
5
for
l
in
a
b
c
d
e
Anyone can help this out?

Here's one way to do it:
$ A=(1 2 3 4); B=(a b c d); for i in $(seq 0 3); do echo ${A[$i]}${B[$i]}; done
1a
2b
3c
4d

In your attempt, the for cases aren't closed with a ;, so it keeps interpreting words in your second for statement as cases for the first for statement.
Use instead:
A="1 2 3 4"
B="a b c d"
for m in $A ; do
for n in $B ; do
echo $m$n
done
done

Related

Bash: Output columns from array consisting of two columns

Problem
I am writing a bash script and I have an array, where each value consists of two columns. It looks like this:
for i in "${res[#]}"; do
echo "$i"
done
#Stream1
0 a1
1 b1
2 c1
4 d1
6 e1
#Stream2
0 a2
1 b2
3 c2
4 d2
9 f2
...
I would like to combine the output from this array into a larger table, and multiplex the indices. Furthermore, I would like to format the top row by inserting comment #Sec.
I would like the result to be something like this:
#Sec Stream1 Stream2
0 a1 a2
1 b1 b2
2 c1
3 c2
4 d1 d2
6 e1
9 f2
The insertion of #Sec and removal of the # behind the Streamkeyword is not necessary but desired if not too difficult.
Tried Solutions
I have tried piping to column and awk, but have not been able to produce the desired results.
EDIT
resis an array in a bash script. It is quite large, so I will only provide a short selection. Running echo "$( typeset -p res)"produces following output:
declare -a res='([1]="#Stream1
0 3072
1 6144
2 5120
3 1024
5 6144
..." [2]="#Stream2
0 3072
1 5120
2 4096
3 3072
53 3072
55 1024
57 2048")'
As for the 'result', my initial intention was to assign the resulting table to a variable and use it in another awk script to calculate the moving averages for specified indices, and plot the results. This will be done for ~20 different files. However I am open to other solutions.
The number of streams may vary from 10 to 50. Each stream having from 100 to 300 rows.
You may use this awk solution:
cat tabulate.awk
NF == 1 {
h = h OFS substr($1, 2)
++numSec
next
}
{
keys[$1]
map[$1,numSec] = $2
}
END {
print h
for (k in keys) {
printf "%s", k
for (i=1; i<=numSec; ++i)
printf "\t%s", map[k,i]
print ""
}
}
Then use it as:
awk -v OFS='\t' -v h='#Sec' -f tabulate.awk file
#Sec Stream1 Stream2
0 a1 a2
1 b1 b2
2 c1
3 c2
4 d1 d2
6 e1
9 f2

how to replace a lasted special character in each row

cat test1
a a 1 a aa 1 1 111 bb b
a1b a 11 b b b
1 asd fdg 1 bb b
I wanna to replace the end "1" shows in each row with #, keep other data as the same.
my expect result
cat expected_result
a a 1 a aa 1 1 11# bb b
a1b a 1# b b b
1 asd fdg # bb b
Could this condition solved by "sed"? I don't know how to select the last "1" in each row, thanks.
Method 1:
1([^1]*)$ matches the last 1 on the line and everything after:
$ sed -E 's/1([^1]*)$/#\1/' test1
a a 1 a aa 1 1 11# bb b
a1b a 1# b b b
1 asd fdg # bb b
Method 2:
(.*)1 matches everything on the line up to and including the last 1:
$ sed -E 's/(.*)1/\1#/' test1
a a 1 a aa 1 1 11# bb b
a1b a 1# b b b
1 asd fdg # bb b
This works because sed's regular expressions are greedy (more precisely, leftmost-longest). The leftmost-longest match of (.*)1 will match from the beginning of the line through the last 1 on the line.
You can try this. * is greedy, tries to match as much as possible and 1/\1#/ will match the last occurrence of 1 of each line and replace with #. If there is something else like 'x' to match and replace the last occurrence with y then it should be x/\1y/
sed 's/\(.*\)1/\1#/' filename
Output:
a a 1 a aa 1 1 11# bb b
a1b a 1# b b b
1 asd fdg # bb b
using rev and awk solution too here.
rev Input_file | awk '{sub(/1/,"#");print}' | rev
Output will be as follows.
a a 1 a aa 1 1 11# bb b
a1b a 1# b b b
1 asd fdg # bb b
1([^1]*$), will match the latest 1 and anything ahead.
sed -r 's/1([^1]*$)/#\1/' v1
cat test#
a a 1 a aa 1 1 11# bb b
a1b a 1# b b b
1 asd fdg # bb b
cat v1
cat test1
a a 1 a aa 1 1 111 bb b
a1b a 11 b b b
1 asd fdg 1 bb b

Print variable inside awk while calculating variable name

I have a script that looks like the example below. I have a letter offset and I need to print the letter that I calculate with the offset. I am not sure how to read that letter using ksh.
My expected answer would be for LETTER_OFFSET(1)=a,LETTER_OFFSET(2)=v, LETTER_OFFSET(3)=c, etc. The offset I have it been calculated inside a loop.
#!/bin/ksh
# 1 2 3 4 5 6 7 8 9 10 11 12
LETTERS=" a v c d g r g s s a g f"
LETTER_OFFSET="3";
Letter=$(echo $LETTERS | awk '{print $((1 * $$LETTER_OFFSET )) }')
You'll pass your offset into your awk script to use as an awk variable using the awk -v flag:
LETTER=$(echo $LETTERS | awk -v offset=$LETTER_OFFSET '{print $offset}')
You don't need to invoke awk in every iteration. You can populate an array using your letters and then access it's values using index:
#!/bin/ksh
# 1 2 3 4 5 6 7 8 9 10 11 12
letters=" a v c d g r g s s a g f"
# populate an array
arr=($letters)
offset=1
while [ "$offset" -le 12 ]; do
echo "${arr[$offset-1]}"
let offset++
done
Output:
a
v
c
d
g
r
g
s
s
a
g
f

Nested for loop - output once

I have a nested for loop to print one letter each from each variable.
for i in a b ; do for j in 1 2; do echo "$i $j"; done; done
a 1
a 2
b 1
b 2
My requirement is to have as
a 1
b 2
How do I get it ?
letters=(a b c d) # declare an array with four elements
numbers=(1 2 3 4)
for ((i=0;i<${#letters[#]};i++)); do echo ${letters[$i]} ${numbers[$i]}; done
Output:
a 1
b 2
c 3
d 4
${#letters[#]} is the number of elements in array letters.
You can also do the same using regular variables and string indexes:
#!/bin/bash
letters="abcdefghi"
nums="123456789"
for ((i = 0; i < ${#nums}; i++)); do
printf "%s %s\n" ${letters:i:1} ${nums:i:1}
done
Output
$ bash prnidx.sh
a 1
b 2
c 3
d 4
e 5
f 6
g 7
h 8
i 9

How to produce cartesian product in bash?

I want to produce such file (cartesian product of [1-3]X[1-5]):
1 1
1 2
1 3
1 4
1 5
2 1
2 2
2 3
2 4
2 5
3 1
3 2
3 3
3 4
3 5
I can do this using nested loop like:
for i in $(seq 3)
do
for j in $(seq 5)
do
echo $i $j
done
done
is there any solution without loops?
Combine two brace expansions!
$ printf "%s\n" {1..3}" "{1..5}
1 1
1 2
1 3
1 4
1 5
2 1
2 2
2 3
2 4
2 5
3 1
3 2
3 3
3 4
3 5
This works by using a single brace expansion:
$ echo {1..5}
1 2 3 4 5
and then combining with another one:
$ echo {1..5}+{a,b,c}
1+a 1+b 1+c 2+a 2+b 2+c 3+a 3+b 3+c 4+a 4+b 4+c 5+a 5+b 5+c
A shorter (but hacky) version of Rubens's answer:
join -j 999999 -o 1.1,2.1 file1 file2
Since the field 999999 most likely does not exist it is considered equal for both sets and therefore join have to do the Cartesian product. It uses O(N+M) memory and produces output at 100..200 Mb/sec on my machine.
I don't like the "shell brace expansion" method like echo {1..100}x{1..100} for large datasets because it uses O(N*M) memory and can when used careless bring your machine to knees. It is hard to stop because ctrl+c does not interrupts brace expansion which is done by the shell itself.
The best alternative for cartesian product in bash is surely -- as pointed by #fedorqui -- to use parameter expansion. However, in case your input that is not easily producible (i.e., if {1..3} and {1..5} does not suffice), you could simply use join.
For example, if you want to peform the cartesian product of two regular files, say "a.txt" and "b.txt", you could do the following. First, the two files:
$ echo -en {a..c}"\tx\n" | sed 's/^/1\t/' > a.txt
$ cat a.txt
1 a x
1 b x
1 c x
$ echo -en "foo\nbar\n" | sed 's/^/1\t/' > b.txt
$ cat b.txt
1 foo
1 bar
Notice the sed command is used to prepend each line with an identifier. The identifier must be the same for all lines, and for all files, so the join will give you the cartesian product -- instead of putting aside some of the resultant lines. So, the join goes as follows:
$ join -j 1 -t $'\t' a.txt b.txt | cut -d $'\t' -f 2-
a x foo
a x bar
b x foo
b x bar
c x foo
c x bar
After both files are joined, cut is used as an alternative to remove the column of "1"s formerly prepended.

Resources