Search code examples
regexperlmakefilegreppiping

Unexpected perl regex substitution output in makefile using fgrep


Inspired by this rule by prwhite on GitHub

help:
    @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//'`

I'm trying to make a version that replaces target files with spaces. I want to replace a part of a string with all spaces. With the file input.txt:

spec: $(ALL_FILES)      ## Runs all specs
myst-spec: $(ALL_FILES) ## Runs just the in-language specs
build: $(SOURCE_FILES)   ## Builds myst into an executable
check: $(ALL_FILES)     ## Runs all crystal specs
clean:                  ## Cleans (deletes) docs and executables
help:                   ## Show this help.

This is the expected output:

spec:                   ## Runs all specs
myst-spec:              ## Runs just the in-language specs
build:                  ## Builds myst into an executable
check:                  ## Runs all crystal specs
clean:                  ## Cleans (deletes) docs and executables
help:                   ## Show this help.

This is the rule i currently have:

help:
    @fgrep -h "##" input.txt | fgrep -v fgrep | \
      perl -pe 's/(.*):(.*)##(.*)/("$1:" . (" " x length($2)) . " $3")/e'

Note that fgrep is used here, because actually this is supposed to be run on the makefile itself, but i use input.txt here so you don't have to deal with the whole makefile.

Unexpectedly, i get this output:

:
:
:
:
:
:

What confuses me the most is that if i run the perl script directly on input.txt (like this: cat input.txt | perl -pe 's/(.*):(.*)##(.*)/("$1:" . (" " x length($2)) . " $3")/e') – i get the expected output. What is the reason it gives different output? I realize it (probably) has something to do with fgrep, but I don't see how it would make a difference, so here I am.


Solution

  • If you want to use $ in a recipe you have to escape it from make by doubling it to $$.

    help:
        @fgrep -h "##" input.txt | fgrep -v fgrep | \
          perl -pe 's/(.*):(.*)##(.*)/("$$1:" . (" " x length($$2)) . " $$3")/e'
    

    The above can be simplified:

    help:
        @perl -ne'next if !/##/; s/(.*):(.*)##(.*)/ "$$1:".( " " x length($$2) )." $$3" /e; print'
    

    Or (5.10+):

    help:
        @perl -ne'next if !/##/; s/:\K(.*)(?=##)/ " " x length($$1) /e; print'