Reputation: 65
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
Upvotes: 1
Views: 345
Reputation: 627082
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, thenlwsp=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.Upvotes: 1
Reputation: 180998
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
\<
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 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.
Upvotes: 1