Git pie - Perl oneliner for Git I often make changes on my code-base and manually using sed, awk or perl -pi -e is not always as efficient as having a simple alias: git pie. 

Furthermore, because I am working on Cygwin, I cannot do real in-place replacement without a temporary file that I have to manually suppress afterwards. 

Last but not least, sed, awk and perl -pi -e touch all the files that make them new from the point of view of make. I rather want to touch only the file with real changes so I wrote this Git alias:

[alias]

    # Replace
    pie = "!f() {                                            \
        git ls-files -z | xargs -0 perl -e '                 \
            my $eval = shift @ARGV;                          \
            foreach my $file (@ARGV) {                       \
                next unless -f $file;                        \
                open my $fd, \"&lt;\", $file;                   \
                my $content = do { local $/; &lt;$fd&gt;};         \
                close $fd;                                   \
                my $new = do { $_ = $content; eval $eval; }; \
                if ($content ne $new) {                      \
                    open my $fd, \"&gt;\", $file;               \
                    print $fd $_;                            \
                    close $fd;                               \
                }                                            \
            }                                                \
        ' \"$1\"; }; f" 


I am sure some simplifications can be made on this piece of code, so I ask for your opinion.

The usage is:

$ git pie s/foo/bar/g


I decided to slurp the whole file in memory in order to do multiline regexes which is very convenient sometime despite the performance. 

There is perhaps a missing feature: the ability to exclude or include some file extensions only or simply allow this call: 

$ git ls-files | grep -P '[.][ch]$' | xargs git pie s/foo/bar/ 


I still didn't figure out how I want to do it. One possible way is: 

$ git pie 's/foo/bar/ if $file =~ /\.(ch)$/'
As written, $new will contain the return value of s///, which is 1 (true) or the empty string (false).  This will (almost) never match $content and you'll be rewriting every file unconditionally.

You can inject $1 directly without the pass-and-eval.  This will stop the Perl script altogether in the event of an error, which is probably desirable.

While my and lexical open etc. are good habits, they're at odds with the design constraints of a one-liner that needs to survive multiple levels of quoting.  For example, print FD prints $_ to handle FD, while print $fd needs an explicit $_ argument, else it prints the string representation of that handle to stdout.  Just switching to a bareword-open saves you five characters.

Perl has a mechanism to read whole files specified on command-line and you can shorten your script further by employing it.

Tying all this together, your one-liner can fit in one line with some whitespace to spare.  In 128 columns (plus four spaces of indent):

[alias]
    pie = "!f(){ git ls-files -z | xargs -0 perl -0777ne'$old=$_; '\"$1\"'; $_ ne $old and open FD, q|&gt;|, $ARGV and print FD';} ;f"