Home > Back-end >  How do I replace a specific number in a text file using a bash script?
How do I replace a specific number in a text file using a bash script?

Time:12-17

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 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"
  • Related