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, \"<\", $file; \
my $content = do { local $/; <$fd>}; \
close $fd; \
my $new = do { $_ = $content; eval $eval; }; \
if ($content ne $new) { \
open my $fd, \">\", $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)$/'
1 Answer 1
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|>|, $ARGV and print FD';} ;f"
git | xargs
directly in the alias? \$\endgroup\$echo 1 2 3 | xargs echo
? You need to write it as follow:f() {echo 1 2 3 | xargs echo 1ドル;}; f
. Do you have a better option? \$\endgroup\$\"<\"
when opening file for reading, as that's the default mode anyway. \$\endgroup\$-i
options of perl & sed etc. The only thing I'd change is to makepie
a standalone perl script in your $PATH (because it could be useful standalone and because reading and editing a perl script inside a couple of levels of quoting is harder than it needs to be, and all those EOL backslashes don't help). then just run thepie
script with xargs in your git alias. \$\endgroup\$open FH, MODE, EXPR
is fine. it's a good habit to use it even when you don't need to. \$\endgroup\$