简体   繁体   中英

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

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

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 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.

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 p; d - print the current line and immediately start the next cycle

    • :1; n; b1 :1; n; b1 - label 1 (reachable only by branching): print the current line and read the n ext 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 sed s 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM