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 theawk
command!x && /^String system = ".*";$/
- ifx
is false and the record starts withString system = "
, then has any text and ends with";
, then{print "String system = \""repl"\";";x=1;next}1
- prints theString system = "
the replacement string";
, assigns1
tox
, 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 instructssed
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 instructssed
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)
- the
t1
- if a substitution was made, then branch to label1
, otherwisep; d
- print the current line and immediately start the next cycle:1; n; b1
- label1
(reachable only by branching): print the current line and read then
ext one, then loop back to label1
. 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.