Home > Software design >  Use bash to find line in java files which include a pattern, and then replace another part of the li
Use bash to find line in java files which include a pattern, and then replace another part of the li

Time:02-26

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

CodePudding user response:

You may create a changesystem.sh file with

#!/bin/bash
for f in *.java; do
    awk -i inplace -v repl="$1" '!x && /^[[:space:]]*String[[:space:]] system[[:space:]]*=[[:space:]]*".*";[[:space:]]*$/{print "    String system = \""repl"\";";x=1;next}1' "$f";
done;

Or, without the use of -i inplace GNU awk feature:

#!/bin/bash
for f in *.java; do
    awk -v repl="$1" '!x && /^[[:space:]]*String[[:space:]] system[[:space:]]*=[[:space:]]*".*";[[:space:]]*$/{print "    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
  • -v repl="$1" passes the first argument of the script to the awk command
  • !x && /^String system = ".*";$/ - if x is false and the record starts with String system = ", then has any text and ends with ";, then
  • {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.

CodePudding user response:

You don't need to pre-compute the line number. The whole job can be done by one not-too-complicated sed command. You probably do want to script it, though. For example:

#!/bin/bash

[[ $# -eq 3 ]] || {
  echo "usage: $0 <context regex> <target regex> <replacement text>" 1>&2
  exit 1
}

sed -si -e "/$1/ { s/\\<$2\\>/$3/; t1; p; d; :1; n; b1; }" ./*.java

That assumes that the files to modify are java source files in the current working directory, and I'm sure you understand the (loose) argument check and usage message.

As for the sed command itself,

  • the -s option instructs sed to treat each argument as a separate stream, instead of operating as if by concatenating all the inputs into one long stream.

  • the -i option instructs sed to modify the designated files in-place.

  • the sed expression takes the default action for each line (printing it verbatim) unless the line matches the "context" pattern given by the first script argument.

  • for lines that do match the context pattern,

    • s/\\<$2\\>/$3/ - attempt to perform the wanted substitution

      • the \< and \> match word start and end boundaries, respectively, so that the specified pattern will not match a partial word (though it can match multiple complete words if the target pattern allows)
    • t1 - if a substitution was made, then branch to label 1, otherwise

    • p; d - print the current line and immediately start the next cycle

    • :1; n; b1 - label 1 (reachable only by branching): print the current line and read the next one, then loop back to label 1. This prints the remainder of the file without any more tests or substitutions.

Example usage:

/path/to/replace_first.sh 'String system' x y

It is worth noting that that does expose the user to some details of seds interpretation of regular expressions and replacement text, though that does not manifest for the example usage.


Note that that could be simplified by removing the context pattern bit if you are sure you want to modify the overall first appearance of the target in each file. You could also hard-code the context, the target pattern, and/or the replacement text. If you hard-code all three then the script would no longer need any argument handling or checking.

  • Related