Good evening- I'm very stuck with what seems like should be a simple sed command. I have a text file (init.input) formatted as follows:
Any help would be appreciated!
100 50 1
100 50 1
2 !The number of additional poutlet condition
45,1,1 762.143895,814.862101,0.999395
46,1,1 762.143895,814.862101,0.999395
Previously in the bash script I have defined the variables cfjlines, var1, var2, var3. In this example, cfjlines specifies the number of lines after that comment line, and is equal to the integer at the beginning of the line.
If for example, var1=123.0, var2=456.0, var3=789.0,
I would like a bash script function which replaces the last three numbers on the last (cfjlines) rows of this file, like so:
100 50 1
100 50 1
2 !The number of additional poutlet condition
45,1,1 123.0,456.0,789.0
46,1,1 123.0,456.0,789.0
All of the numbers shown in this example will change in future files, but the position is important. My attempt to assign the values to an array and then read for that has failed: ```
# Extract the line containing the integer value from the input file
line=$(grep "!The number of additional poutlet condition" init.input)
# Extract the integer value from the line
cfjlines=$(cut -d' ' -f2 <<< "$line")
declare -a numbers
# Extract the last two lines of the file
last_lines=$(tail -n $cfjlines init.input)
# Delete last rows of init.input
head -n -$cfjlines init.input > temp.txt ; mv temp.txt init.input
# Loop through the lines of the last lines
while read -r line; do
# Replace spaces with commas in the line
modified_line=$(echo "$line" | tr ' ' ',')
# Append the modified line to the end of the original file
echo "$modified_line" >> init.input
done <<< "$last_lines"
# Set the IFS (internal field separator) to a comma
IFS=','
mapfile -t array < <(tail -n 1 init.input)
# Iterate over the elements in the array
for element in "${array[@]}"; do
# Split the element into fields using the IFS
read -ra fields <<< "$element"
# Replace the last three fields with var1, var2, and var3
fields[-3]="var1"
fields[-2]="var2"
fields[-1]="var3"
# Join the fields back together using the IFS and print the result
printf "%s\n" "${fields[*]}"
done
```
This last part gives me an error saying the fields index is a bad array subscript, but I think that's because I'm not properly getting the six values from the last lines into six array elements.
Again, any help is appreciated. Thanks!
CodePudding user response:
As many others will likely advise you, bash is not a very efficient tool for doing content manipulation. So below, I provide a solution that is awk based.
Your question seems to indicate that the first 3 lines are static and are, therefore, passed thru as is.
Your question also implies that all the remaining data lines will have the second grouping modified to the same 3 specified values. You could pass those values as separate values (per the below script's command line positions), or you could pass them thru as a single, pre-formatted string, IF that is not dependant on the contents of those lines.
If you need the values to be line-based, context-driven, based on values on each line, then you would need to remove the comment prefix from the line doing the split for the "data" array, and assess/manipulate those values before generating appropriate substitutes before the following printf statements.
#!/bin/sh
if [ $# -eq 3 ]
then
VAR1=$1
VAR2=$2
VAR3=$3
else
VAR1=123.0
VAR2=456.0
VAR3=789.0
fi
echo "\
100 50 1
100 50 1
2 !The number of additional poutlet condition
45,1,1 762.143895,814.862101,0.999395
46,1,1 762.143895,814.862101,0.999395" |
awk -v var1="${VAR1}" -v var2="${VAR2}" -v var3="${VAR3}" '{
if( NR < 4 ){
### do not modify first 3 lines
print $0 ;
}else{
### split line into 2 groupings
split( $0, raw ) ;
### IF ncessary, split second grouping into distinct array values
#split( raw[2], data, "," ) ;
#for( i=1 ; i <= 3 ; i ){
# printf("\t data[%s] = %s\n", i, data[i] ) | "cat >&2 " ;
#} ;
### pass thru first grouping unmodified
printf("%s ", raw[1] ) ;
### replace 3 values on lines
printf("%s,%s,%s\n", var1, var2, var3 ) ;
} ;
}'
This is session's execution log:
ericthered@OasisMega1:/WORKS$ script.sh 999 777 333
100 50 1
100 50 1
2 !The number of additional poutlet condition
45,1,1 999,777,333
46,1,1 999,777,333
ericthered@OasisMega1:/WORKS$
or with the default values you provided,
ericthered@OasisMega1:/WORKS$ script.sh
100 50 1
100 50 1
2 !The number of additional poutlet condition
45,1,1 123.0,456.0,789.0
46,1,1 123.0,456.0,789.0
ericthered@OasisMega1:/WORKS$
CodePudding user response:
With your given input, this is how I would do it.
#!/usr/bin/env bash
var1=123.0
var2=456.0
var3=789.0
last_fields=3
file=init.input
cfjlines=$(
grep -Po '(?<=\s)\d (?=\s!The number of additional poutlet condition)' "$file"
)
start=0
cfjlines=3
while IFS= read -ru3 line; do
if ((start >= cfjlines)); then
IFS=' ' read -ra fields <<< "${line//,/ }"
fields[-1]="$var3"
fields[-2]="$var2"
fields[-3]="$var1"
temp=$(IFS=','; printf '%s\n' "${fields[*]:last_fields}")
temp1="${fields[*]::last_fields}"
printf '%s\n' "${temp1// /,} $temp"
else
printf '%s\n' "$line"
fi
done 3< "$file"