Friday, April 15, 2022

[SOLVED] Use bash to find line in java files which include a pattern, and then replace another part of the line

Issue

I have a directory that includes a lot of java files, and in each file I have a class variable:

String system = "x";

I want to be able to create a bash script which I execute in the same directory, which will go to only the java files in the directory, and replace this instance of x, with y. Here x and y are a word. Now this may not be the only instance of the word x in the java script, however it will definitely be the first.

I want to be able to execute my script in the command line similar to:

changesystem.sh -x -y

This way I can specify what the x should be, and the y I wish to replace it with. I found a way to find and print the line number at which the first instance of a pattern is found:

awk '$0 ~ /String system/ {print NR}' file

I then found how to replace a substring on a given line using:

awk 'NR==line_number { sub("x", "y") }'

However, I have not found a way to combine them. Maybe there is also an easier way? Or even, a better and more efficient way?

Any help/advice will be greatly appreciated


Solution

You may create a changesystem.sh file with the following GNU awk script:

#!/bin/bash
for f in *.java; do
    awk -i inplace -v repl="$1" '
        !x && /^\s*String\s+system\s*=\s*".*";\s*$/{
            lwsp=gensub(/\S.*/, "", 1);
            print lwsp"String system = \""repl"\";";
            x=1;next;
        }1' "$f";
done;

Or, with any awk:

#!/bin/bash
for f in *.java; do
    awk -v repl="$1" '
        !x && /^[[:space:]]*String[[:space:]]+system[[:space:]]*=[[:space:]]*".*";[[:space:]]*$/{
            lwsp=$0; sub(/[^[:space:]].*/, "", lwsp);
            print lwsp"String system = \""repl"\";";
            x=1;next
        }1' "$f" > tmp && mv tmp "$f";
done;

Then, make the file executable:

chmod +x changesystem.sh

Then, run it like

./changesystem.sh 'new_value'

Notes:

  • for f in *.java; do ... done iterates over all *.java files in the current directory
  • -i inplace - GNU awk feature to perform replacement inline (not available in a non-GNU awk)
  • -v repl="$1" passes the first argument of the script to the awk command
  • !x && /^\s*String\s+system\s*=\s*".*";\s*$/ - if x is false and the record starts with any amount of whitespace (\s* or [[:space:]]*), then String, any 1+ whitespaces, system, = enclosed with any zero or more whitesapces, and then a " char, then has any text and ends with "; and any zero or more whitespaces, then
  • lwsp=gensub(/\S.*/, "", 1); puts the leading whitespace in the lwsp variable (it removes all text starting with the first non-whitespace char from the line matched)
  • lwsp=$0; sub(/[^[:space:]].*/, "", lwsp); - same as above, just in a different way since gensub is not supported in non-GNU awk and sub modifies the given input string (here, lwsp)
  • {print "String system = \""repl"\";";x=1;next}1 - prints the String system = " + the replacement string + ";, assigns 1 to x, and moves to the next line, else, just prints the line as is.


Answered By - Wiktor Stribiżew
Answer Checked By - Marilyn (WPSolving Volunteer)