views:

491

answers:

7

I'm thinking of using find or grep to collect the files, and maybe sed to make the change, but what to do with the output? Or would it be better to use "argdo" in vim?

Note: this question is asking for command line solutions, not IDE's. Answers and comments suggesting IDE's will be calmly, politely and serenely flagged. :-)

+1  A: 

I'd suggest sed -i to obviate the need to worry about the output. Since you don't specify your platform, check your man pages; the semantics of sed -i vary from Linux to BSD.

chaos
Thanks, I did not know that. (I've tried redirecting output to the original file, but that makes it zero length).
13ren
+1  A: 

I would use sed if there was a decent way to so "do this for the first line only" but I don't know of one off of the top of my head. Why not use perl instead. Something like:

find . -name '*.java' -exec perl -p -i.bak -e '
    BEGIN {
      print "import package.name.*;\n"
    }' {} \;

should do the job. Check perlrun(1) for more details.

D.Shawley
Thanks, I agree that Perl is a sensible choice for this kind of text processing, though I just got sed to do the inserting part: sed -ie '1i\\nimport package.name.*;' Eg.java # "1" is the first line; "i" is insert
13ren
Nice. I couldn't remember if you could use a bare line number as an address in sed or not. The other choice would have been awk, but I tend towards perl if it isn't basic substitution.
D.Shawley
+1  A: 

I am huge fan of the following

export MYLIST=`find . -type f -name *.java`
for a in $MYLIST; do
   mv $a $a.orig
   echo "import.stuff" >> $a
   cat $a.orig >> $a
   chmod 755 $a 
done;

mv is evil and eventually this will get you. But I use this same construct for a lot of things and it is my utility knife of choice.

Update: This method also backs up the files which you should do using any method. In addition it does not use anything but the shell's features. You don't have to jog your memory about tools you don't use often. It is simple enough to teach a monkey (and believe me I have) to do. And you are generally wise enough to just throw it away because it took four seconds to write.

ojblass
I wanted to avoid temporary files, but you're right: if it works, why not?
13ren
Its sole advantage is that it needs nothing external from the shells functionality. It works in bash and ksh and it is faster than going refreshing your mind on perl and other silly little things.
ojblass
Good points in your update, esp about backing up as a side-effect.
13ren
+1  A: 

you can use sed to insert a line before the first line of the file:

sed -ie "1i import package.name.*;" YourClass.java

use a for loop to iterate through all your files and run this expression on them. but be careful if you have packages, because the import statements must be after the package declaration. you can use a more complex sed expression, if that's the case.

cd1
Thanks, that's what I came up with after seeing chaos's answer, except for the non-significant whitespace between "1i" and "import". I didn't know that was allowed - it's much clearer. sed can append after matching lines, like sed -ie "/^package/a import package.name.*;" Eg.java # but it will match any other line beginning with package also, not just the first one...
13ren
+1  A: 
for i in `ls *java`
do 
  sed -i '.old' '1 i\
  Your include statement here.
  ' $i
done

Should do it. -i does an in place replacement and .old saves the old file just in case something goes wrong. Replace the iterator *java as necessary (maybe 'find . | grep java' or something instead.)

Justin Scheiner
Thanks, that must be the [SUFFIX] part of the man page - it makes more sense after seeing an example.
13ren
+1  A: 

You may also use the ed command to do in-file search and replace:

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw'

see: http://bash-hackers.org/wiki/doku.php?id=howto:edit-ed

A: 

I've actually starting to do it using "argdo" in vim. First of all, set the args:

:args **/*.java

The "**" traverses all the subdir, and the "args" sets them to be the arg list (as if you started vim with all those files in as arguments to vim, eg: vim package1/One.java package1/Two.java package2/One.java)

Then fiddle with whatever commands I need to make the transform I want, eg:

:/^package.*$/s/$/\rimport package.name.*;/

The "/^package.*$/" acts as an address for the ordinary "s///" substitution that follows it; the "/$/" matches the end of the package's line; the "\r" is to get a newline.

Now I can automate this over all files, with argdo. I hit ":", then uparrow to get the above line, then insert "argdo " so it becomes:

:argdo /^package.*$/s/$/\rimport package.name.*;/

This "argdo" applies that transform to each file in the argument list.

What is really nice about this solution is that it isn't dangerous: it hasn't actually changed the files yet, but I can look at them to confirm it did what I wanted. I can undo on specific files, or I can exit if I don't like what it's done (BTW: I've mapped ^n and ^p to :n and :N so I can scoot quickly through the files). Now, I commit them with ":wa" - "write all" files.

:wa

At this point, I can still undo specific files, or finesse them as needed.

This same approach can be used for other refactorings (e.g. change a method signature and calls to it, in many files).


BTW: This is clumsy: "s/$/\rtext/"... There must be a better way to append text from vim's commandline...

13ren
For general refactoring you should honestly take eclipse for a ride if you are going to open an IDE. I am a huge VIM bigot (see my avatar) but the GUIs that support vararg enabled languages will truly be your undoing as I learned doing almost the identical to the above.
ojblass
Well, I wouldn't take that from anyone else but given your avatar... :-) I don't understand your second sentence: What do you mean by "vararg" enabled languages being your undoing? (I'm using Java, but I don't use variable arguments very often)
13ren