58

I have a file (more specifically, a log4j configuration file) and I want to be able to read in the file and pick out certain lines in the code and replace them. For example, within the file there is a string of text that indicates the directory it is stored in, or the level of the logger. I want to be able to replace those string of text without reading in the file, writing it to another file, and deleting the original file. Is there a more efficient way of doing find and replace texts in a file using Java?

Here is an example of the text file I'm trying to work with:

log4j.rootLogger=DEBUG, A0

log4j.appender.A0=org.apache.log4j.RollingFileAppender
log4j.appender.A0.File=C:/log.txt
log4j.appender.A0.MaxFileSize=100KB
log4j.appender.A0.MaxBackupIndex=1

log4j.appender.A0.layout=org.apache.log4j.RollingFileAppender
log4j.appender.A0.layout.ConversionPattern=%-4r [%t] %-5p: %c %x - %m%n

I want to be able to read the file and replace 'DEBUG' with another level or replace the file directory name 'C:/log.txt'. The log configuration file is also written in xml. An example of that is featured below.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration>
    <appender class="org.apache.log4j.RollingFileAppender" name="A0">
        <param name="append" value="false"/>
        <param name="File" value="C:/log/.txt"/>
        <param name="MaxBackupIndex" value="1"/>
        <param name="MaxFileSize" value="100KB"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-4r [%t] %-5p: %c %x - %m%n"/>
        </layout>
    </appender>
    <root>
        <level value="DEBUG"/>
        <appender-ref ref="A0"/>
    </root>
</log4j:configuration>

I'm thinking it may be possible to use a hash map for this type of implementation?

afuzzyllama
  • 6,275
  • 5
  • 42
  • 61
user459811
  • 2,626
  • 9
  • 32
  • 61

6 Answers6

153

Any decent text editor has a search&replace facility that supports regular expressions.

If however, you have reason to reinvent the wheel in Java, you can do:

Path path = Paths.get("test.txt");
Charset charset = StandardCharsets.UTF_8;

String content = new String(Files.readAllBytes(path), charset);
content = content.replaceAll("foo", "bar");
Files.write(path, content.getBytes(charset));

This only works for Java 7 or newer. If you are stuck on an older Java, you can do:

String content = IOUtils.toString(new FileInputStream(myfile), myencoding);
content = content.replaceAll(myPattern, myReplacement);
IOUtils.write(content, new FileOutputStream(myfile), myencoding);

In this case, you'll need to add error handling and close the streams after you are done with them.

IOUtils is documented at http://commons.apache.org/proper/commons-io/javadocs/api-release/org/apache/commons/io/IOUtils.html

meriton
  • 61,876
  • 13
  • 96
  • 163
  • 3
    What about closing the FileInputStream & FileOutputStream? – Asaf Pinhassi Dec 08 '12 at 10:37
  • 10
    That is left as an exercise to the reader (see the sentence after the code sample). – meriton Dec 08 '12 at 14:15
  • 6
    You are loading the entire file in memory, which is probably safe in this case. But what about bigger files (e.g. 2 GB)? The difficult (but right thing to do) is streaming the file from source to destination I think. – bvdb Oct 24 '14 at 15:57
  • The question talks about log4j configuration files, which are commonly smaller than 10 KB. Implementing streaming regular expression replacement for such a task is premature optimization in my opinion. But yes, if you must process files bigger then about 100 MB, streaming is likely worth it. – meriton Oct 24 '14 at 18:32
  • What's the difference between `replace` and `replaceAll`? From what I remember, they both replace all instances. – user3932000 Mar 23 '17 at 23:39
  • 10
    I don't understand the attitude of the first part of this question. What if someone wants to modify the file as part of a program or integration test. How is that reinventing the wheel? – Novaterata May 01 '17 at 14:27
  • 2
    This is extremely useful for automation and should not be frowned upon. – Duncan Calvert Jun 19 '17 at 19:58
  • 2
    For 6 years, people were happy with this answer, and nobody felt a need to complain about "attitude", "frowning" or "bitterness". Interesting how community standards have changed ... the flippant "if you have reason to reinvent the wheel" was simply there to reinforce the notion that search and replace is an existing feature one should not duplicate without a reason. And might I remind you that this question was about log4j configuration? If one wants to modify log4j configuration as part of an integration test, I'd recommend the log4j Java API over search&replace any time ... – meriton Jun 19 '17 at 22:19
  • 1
    I would suggest changing `replaceAll` with `replace`. Both methods replace *all* occurrences of specified target. ***Only*** difference between both methods is that `replaceAll` supports regex (regular expression) syntax, while `replace` replaces literals (also *all* of them like `"abac".replace("a","X")` would result in `XbXc`). – Pshemo Aug 26 '18 at 13:25
20

After visiting this question and noting the initial concerns of the chosen solution, I figured I'd contribute this one for those not using Java 7 which uses FileUtils instead of IOUtils from Apache Commons. The advantage here is that the readFileToString and the writeStringToFile handle the issue of closing the files for you automatically. (writeStringToFile doesn't document it but you can read the source). Hopefully this recipe simplifies things for anyone new coming to this problem.

  try {
     String content = FileUtils.readFileToString(new File("InputFile"), "UTF-8");
     content = content.replaceAll("toReplace", "replacementString");
     File tempFile = new File("OutputFile");
     FileUtils.writeStringToFile(tempFile, content, "UTF-8");
  } catch (IOException e) {
     //Simple exception handling, replace with what's necessary for your use case!
     throw new RuntimeException("Generating file failed", e);
  }
ojintoad
  • 403
  • 3
  • 5
  • 3
    No need for replaceAll, just use replace. The first one uses regex. The second one just replaces all found String content – Christian Jul 26 '18 at 06:48
3
public static void replaceFileString(String old, String new) throws IOException {
    String fileName = Settings.getValue("fileDirectory");
    FileInputStream fis = new FileInputStream(fileName);
    String content = IOUtils.toString(fis, Charset.defaultCharset());
    content = content.replaceAll(old, new);
    FileOutputStream fos = new FileOutputStream(fileName);
    IOUtils.write(content, new FileOutputStream(fileName), Charset.defaultCharset());
    fis.close();
    fos.close();
}

above is my implementation of Meriton's example that works for me. The fileName is the directory (ie. D:\utilities\settings.txt). I'm not sure what character set should be used, but I ran this code on a Windows XP machine just now and it did the trick without doing that temporary file creation and renaming stuff.

joshpt
  • 683
  • 1
  • 7
  • 17
  • by the way, I was using "org.apache.commons.configuration.PropertiesConfiguration" for the Settings class which is just a key/value properties file – joshpt Jan 04 '13 at 18:13
1

You might want to use Scanner to parse through and find the specific sections you want to modify. There's also Split and StringTokenizer that may work, but at the level you're working at Scanner might be what's needed.

Here's some additional info on what the difference is between them: Scanner vs. StringTokenizer vs. String.Split

Community
  • 1
  • 1
Ryan P.
  • 845
  • 2
  • 14
  • 20
1

This is the sort of thing I'd normally use a scripting language for. It's very useful to have the ability to perform these sorts of transformations very simply using something like Ruby/Perl/Python (insert your favorite scripting language here).

I wouldn't normally use Java for this since it's too heavyweight in terms of development cycle/typing etc.

Note that if you want to be particular in manipulating XML, it's advisable to read the file as XML and manipulate it as such (the above scripting languages have very useful and simple APIs for doing this sort of work). A simple text search/replace can invalidate your file in terms of character encoding etc. As always, it depends on the complexity of your search/replace requirements.

Brian Agnew
  • 254,044
  • 36
  • 316
  • 423
0

You can use Java's Scanner class to parse words of a file and process them in your application, and then use a BufferedWriter or FileWriter to write back to the file, applying the changes.

I think there is a more efficient way of getting the iterator's position of the scanner at some point, in order to better implement editting. But since files are either open for reading, or writing, I'm not sure regarding that.

In any case, you can use libraries already available for parsing of XML files, which have all of this implemented already and will allow you to do what you want easily.

Luis Miguel Serrano
  • 4,809
  • 2
  • 37
  • 40