Insert spaces in the string array - arrays

I have the following array:
declare -a case=("060610" "080813" "101016" "121219" "141422")
I want to generate another array where the elements have whitespaces inserted appropriately as:
"06 06 10" "08 08 13" "10 10 16" "12 12 19" "14 14 22"
I got till handling the elements individually using sed as:
echo '060610' | sed 's/../& /g'
But I am not being able to do it while using the array. The sed confuses the white spaces in between the elements and I am left with the output:
echo "${case[#]}" | sed 's/../& /g'
Gives me:
06 06 10 0 80 81 3 10 10 16 1 21 21 9 14 14 22
Can someone help?

You need to loop over the array, not echo it as a whole, because you don't get the grouping when you echo it.
declare -a newcase
for c in "${case[#]}"
do
newcase+=("$(echo "$c" | sed 's/../& /g')")
done

You can use printf '%s\n' "${case[#]}" | sed 's/../& /g' to get each number on a separate line and avoiding the space problem:
$ declare -a case=("060610" "080813" "101016" "121219" "141422")
$ printf '%s\n' "${case[#]}" | sed 's/../& /g'
06 06 10
08 08 13
10 10 16
12 12 19
14 14 22
If you want it back into an array, you can use mapfile

Related

How to pipe the output of a sed command into a function in unix

I have this C function that I am using to output any amount of integer that is input.
/*
This program will be called function.c
*/
#include <stdio.h>
int main (void) {
int num;
while(scanf("%d",&num)==1) {
printf("You entered: %d\n",num);
}
return 0;
}
In my shell the function works like this as expected with the pipe command
$echo "1 7 3 " | ./function
Output:
You entered: 1
You entered: 7
You entered: 3
Now what I'm trying to do is use the sed command on a csv file and pipe the output into my function.
Here is my CSV file
$cat file.csv
Output:
2,2,3,3,8
Using the sed command I remove the commas
$sed 's/,/ /g' file.csv
Output:
2 2 3 3 8
Now the issue I have is when I try to use the output of the sed command to pipe the numbers into my function:
$sed 's/,/ /g' file.csv | ./function
I get no output. I don't know if there is a syntax error, but I believe I should be able to do this with a csv file.
I say you have a BOM in "file.csv".
That will stop ./function from scanning the first number.
$ hexdump -C file.csv
00000000 ef bb bf 32 2c 32 2c 33 2c 33 2c 38 0a |...2,2,3,3,8.|
0000000d
$ sed 's/,/ /g' file.csv |hexdump -C
00000000 ef bb bf 32 20 32 20 33 20 33 20 38 0a |...2 2 3 3 8.|
0000000d
$ cat file.csv
2,2,3,3,8
$ cut -b4- file.csv |sed 's/,/ /g' |./function
You entered: 2
You entered: 2
You entered: 3
You entered: 3
You entered: 8
$ sed 's/,/ /g' file.csv |cut -b4- |./function
You entered: 2
You entered: 2
You entered: 3
You entered: 3
You entered: 8

Picking input record fields with AWK

Let's say we have a shell variable $x containing a space separated list of numbers from 1 to 30:
$ x=$(for i in {1..30}; do echo -n "$i "; done)
$ echo $x
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
We can print the first three input record fields with AWK like this:
$ echo $x | awk '{print $1 " " $2 " " $3}'
1 2 3
How can we print all the fields starting from the Nth field with AWK? E.g.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
EDIT: I can use cut, sed etc. to do the same but in this case I'd like to know how to do this with AWK.
Converting my comment to answer so that solution is easy to find for future visitors.
You may use this awk:
awk '{for (i=3; i<=NF; ++i) printf "%s", $i (i<NF?OFS:ORS)}' file
or pass start position as argument:
awk -v n=3 '{for (i=n; i<=NF; ++i) printf "%s", $i (i<NF?OFS:ORS)}' file
Version 4: Shortest is probably using sub to cut off the first three fields and their separators:
$ echo $x | awk 'sub(/^ *([^ ]+ +){3}/,"")'
Output:
4 5 6 7 8 9 ...
This will, however, preserve all space after $4:
$ echo "1 2 3 4 5" | awk 'sub(/^ *([^ ]+ +){3}/,"")'
4 5
so if you wanted the space squeezed, you'd need to, for example:
$ echo "1 2 3 4 5" | awk 'sub(/^ *([^ ]+ +){3}/,"") && $1=$1'
4 5
with the exception that if there are only 4 fields and the 4th field happens to be a 0:
$ echo "1 2 3 0" | awk 'sub(/^ *([^ ]+ +){3}/,"")&&$1=$1'
$ [no output]
in which case you'd need to:
$ echo "1 2 3 0" | awk 'sub(/^ *([^ ]+ +){3}/,"") && ($1=$1) || 1'
0
Version 1: cut is better suited for the job:
$ cut -d\ -f 4- <<<$x
Version 2: Using awk you could:
$ echo -n $x | awk -v RS=\ -v ORS=\ 'NR>=4;END{printf "\n"}'
Version 3: If you want to preserve those varying amounts of space, using GNU awk you could use split's fourth parameter seps:
$ echo "1 2 3 4 5 6 7" |
gawk '{
n=split($0,a,FS,seps) # actual separators goes to seps
for(i=4;i<=n;i++) # loop from 4th
printf "%s%s",a[i],(i==n?RS:seps[i]) # get fields from arrays
}'
Adding one more approach to add all value into a variable and once all fields values are done with reading just print the value of variable. Change the value of n= as per from which field onwards you want to get the data.
echo "$x" |
awk -v n=3 '{val="";for(i=n; i<=NF; i++){val=(val?val OFS:"")$i};print val}'
With GNU awk, you can use the join function which has been a built-in include since gawk 4.1:
x=$(seq 30 | tr '\n' ' ')
echo "$x" | gawk '#include "join"
{split($0, arr)
print join(arr, 4, length(arr), "|")}
'
4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30
(Shown here with a '|' instead of a ' ' for clarity...)
Alternative way of including join:
echo "$x" | gawk -i join '{split($0, arr); print join(arr, 4, length(arr), "|")}'
Using gnu awk and gensub:
echo $x | awk '{ print gensub(/^([[:digit:]]+[[:space:]]){3}(.*$)/,"\\2",$0)}'
Using gensub, split the string into two sections based on regular expressions and print the second section only.

Bad substitution error when passing decremented variable to "sed" command

I like to use "sed" command to delete two consecutive lines from a file.
I can delete single line using following syntax where
variable "index" holds the line number:
sed -i "${index}d" "$PWD$DEBUG_DIR$DEBUG_MENU"
Since I like to delete consecutive lines I did test this
syntax which supposedly decrements / increments the variable but I am getting a bad substitution error.
Addendum
I am not sure where to post this , but during troubleshooting of
this issue I have discovered that this syntax does not work as expected
(( index++)) nor (( index-- ))
Using sed with "index" in single line deletion twice in a row / sequentially / works and resolves few issues.
#delete command line
sed -i "${index}d" "$PWD$DEBUG_DIR$DEBUG_MENU"
#delete description line
sed -i "${index}d" "$PWD$DEBUG_DIR$DEBUG_MENU"
sed is the right tool for doing s/old/new, that is all. What you're trying to do isn't that so why are you trying to coerce sed into doing something when there's far more appropriate tools can do the job far easier?
The right way to do what your script:
#retrieve first matching line number
index=$(sed -n "/$choice/=" "$PWD$DEBUG_DIR$DEBUG_MENU")
#delete matching line plus next line from file
sed -i "/$index[1], (( $index[1]++))/"
"$PWD$DEBUG_DIR$DEBUG_MENU"
seems to be trying to do is this:
awk -v choice="$choice" '$0~choice{skip=2} !(skip&&skip--)' "$PWD$DEBUG_DIR$DEBUG_MENU"
For example:
$ seq 20 | awk -v choice="4" '$0~choice{skip=2} !(skip&&skip--)'
1
2
3
6
7
8
9
10
11
12
13
16
17
18
19
20
if you only want to delete the first match:
$ seq 20 | awk -v choice="4" '$0~choice{skip=2;cnt++} !(cnt==1 && skip && skip--)'
1
2
3
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
or the 2nd:
$ seq 20 | awk -v choice="4" '$0~choice{skip=2;cnt++} !(cnt==2 && skip && skip--)'
1
2
3
4
5
6
7
8
9
10
11
12
13
16
17
18
19
20
and to skip 5 lines instead of 2:
$ seq 20 | awk -v choice="4" '$0~choice{skip=5} !(skip&&skip--)'
1
2
3
9
10
11
12
13
19
20
Just use the right tool for the right job, don't go digging holes to plant trees with a teaspoon.
If you just want the first one, then quit when you see it:
sed -n "/$choice/ {=;q}" file
But you look like you're processing this file multiple times. There must be a simpler way to do it, if you can describe your over-arching goal.
For example, if you just want to remove the matched line and the next line, but only the first time, you can use awk: here we see "4" and "5" are gone, but "14" and "15" remain:
$ seq 20 | awk '/4/ && !seen {getline; seen++; next} 1'
1
2
3
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
With GNU sed, you can use +1 as the second address in an address range:
index=4
seq 10 | sed "$index,+1 d"
Or if you want to use bash/ksh arithmetic expansion: use the post-increment operator
seq 10 | sed "$((index++)),$index d"
Note here that, due to the pipeline, sed is running in a subshell: even though the index value is now 5, this occurs in a subshell. After the sed command ends, index has value 4 in the current shell.

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

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.

Resources