How do I run a Perl one liner from a makefile? - perl

I know the perl one liner below is very simple, works and does a global substitution, A for a; but how do I run it in a makefile?
perl -pi -e "s/a/A/g" filename
I have tried (I now think the rest of the post is junk as the shell command does a command line expansion - NOT WHAT I WANT!) The question above still stands!
APP = $(shell perl -pi -e "s/a/A/g" filename)
with and without the following line
EXE = $(APP)
and I always get the following error
make: APP: Command not found
which I assume comes from the line that starts APP
Thanks

If you want to run perl as part of a target's action, you might use
$ cat Makefile
all:
echo abc | perl -pe 's/a/A/g'
$ make
echo abc | perl -pe 's/a/A/g'
Abc
(Note that there's a TAB character before echo.)
Perl's -i option is for editing files in-place, but that will confuse make (unless perhaps you're writing a phony target). A more typical pattern is to make targets from sources. For example:
$ cat Makefile
all: bAr
bAr: bar.in
perl -pe 's/a/A/g' bar.in > bAr
$ cat bar.in
bar
$ make
perl -pe 's/a/A/g' bar.in > bAr
$ cat bAr
bAr
If you let us know what you're trying to do, we'll be able to give you better, more helpful answers.

You should show the smallest possible Makefile which demonstrates your problem, and show how you are calling it. Assuming your Makefile looks something like this, I get the error message. Note that there is a tab character preceding the APP in the all: target.
APP = $(shell date)
all:
APP
Perhaps you meant to do this instead:
APP = $(shell date)
all:
$(APP)
I did not use your perl command because it does not run for me as-is.
Do you really mean to use Perl's substitution operator? perl -pi -e "s/a/A/g"
Here is a link to GNU make documentation.

Related

How do I do unbuffered substitution in a perl oneliner?

I've got a bash script that wraps mvn (Apache Maven) to add colour to its output. A cut-down version of what it does is:
mvn "$#" | sed -e "s/^\[INFO\] \-.*/$bldblu&$rst/g"
where $bldblu is the ANSI color escape characters for bold blue, and $rst resets the colours.
The issue I'm having is that sometimes mvn writes a line that doesn't end in a newline, thus (as far as I can tell) sed keeps waiting for input and never prints the prompt (which makes it seem like Maven is hanging). I've tried adding -u to sed but that just forces sed to do line-by-line buffering instead of buffering more than one line - not helpful for me.
So far this is what I've come up with:
mvn "$#" | perl -pe "$| = 1; s/^(\[INFO\] \-.*)/$bldblu\$1$rst/g"
but I think the use of -p is not correct here. Any help?
A substitution may be overkill, especially when the replacement pattern has special characters in it. How about this?
export bldblu
export rst
mvn "$#" | perl -pe 'if(/^.INFO. -/){ $_=$ENV{bldblu}.$_.$ENV{rst} }'
or rather than reinventing the wheel
mvn "$#" | perl -MTerm::ANSIColor -pe
'$_=color("bold blue").$_.color("reset") if /^.INFO. -/'
(workaround) Use sed --unbuffered
I couldn't figure out the solution but thankfully this is good enough for my particular usage:
cat - | sed --unbuffered 's/.*?from//g'
But I too would like to know the answer. Perl one line substitution is a key idiom in my toolbelt.
BSD
Looks like there is no common flag for GNU and BSD. For the latter, you'd need:
-l Make output line buffered.

In-place replacement

I have a CSV. I want to edit the 35th field of the CSV and write the change back to the 35th field. This is what I am doing on bash:
awk -F "," '{print $35}' test.csv | sed -i 's/^0/+91/g'
so, I am pulling the 35th entry using awk and then replacing the "0" in the starting position in the string with "+91". This one works perfet and I get desired output on the console.
Now I want this new entry to get written in the file. I am thinking of sed's "in -place" replacement feature but this fetuare needs and input file. In above command, I cannot provide input file because my primary command is awk and sed is taking the input from awk.
Thanks.
You should choose one of the two tools. As for sed, it can be done as follows:
sed -ri 's/^(([^,]*,){34})0([^,]*)/\1+91\3/' test.csv
Not sure about awk, but #shellter's comment might help with that.
The in-place feature of sed is misnamed, as it does not edit the file in place. Instead, it creates a new file with the same name. eg:
$ echo foo > foo
$ ln -f foo bar
$ ls -i foo bar # These are the same file
797325 bar 797325 foo
$ echo new-text > foo # Changes bar
$ cat bar
new-text
$ printf '/new/s//newer\nw\nq\n' | ed foo # Edit foo "in-place"; changes bar
9
newer-text
11
$ cat bar
newer-text
$ ls -i foo bar # Still the same file
797325 bar 797325 foo
$ sed -i s/new/newer/ foo # Does not edit in-place; creates a new file
$ ls -i foo bar
797325 bar 792722 foo
Since sed is not actually editing the file in place, but writing a new file and then renaming it to the old file, you might as well do the same.
awk ... test.csv | sed ... > test.csv.1 && mv test.csv.1 test.csv
There is the misperception that using sed -i somehow avoids the creation of the temporary file. It does not. It just hides the fact from you. Sometimes abstraction is a good thing, but other times it is unnecessary obfuscation. In the case of sed -i, it is the latter. The shell is really good at file manipulation. Use it as intended. If you do need to edit a file in place, don't use the streaming version of ed; just use ed
So, it turned out there are numerous ways to do it. I got it working with sed as below:
sed -i 's/0\([0-9]\{10\}\)/\+91\1/g' test.csv
But this is little tricky as it will edit any entry which matches the criteria. however in my case, It is working fine.
Similar implementation of above logic in perl:
perl -p -i -e 's/\b0(\d{10})\b/\+91$1/g;' test.csv
Again, same caveat as mentioned above.
More precise way of doing it as shown by Lev Levitsky because it will operate specifically on the 35th field
sed -ri 's/^(([^,]*,){34})0([^,]*)/\1+91\3/g' test.csv
For more complex situations, I will have to consider using any of the csv modules of perl.
Thanks everyone for your time and input. I surely know more about sed/awk after reading your replies.
This might work for you:
sed -i 's/[^,]*/+91/35' test.csv
EDIT:
To replace the leading zero in the 35th field:
sed 'h;s/[^,]*/\n&/35;/\n0/!{x;b};s//+91/' test.csv
or more simply:
|sed 's/^\(\([^,]*,\)\{34\}\)0/\1+91/' test.csv
If you have moreutils installed, you can simply use the sponge tool:
awk -F "," '{print $35}' test.csv | sed -i 's/^0/+91/g' | sponge test.csv
sponge soaks up the input, closes the input pipe (stdin) and, only then, opens and writes to the test.csv file.
As of 2015, moreutils is available in package repositories of several major Linux distributions, such as Arch Linux, Debian and Ubuntu.
Another perl solution to edit the 35th field in-place:
perl -i -F, -lane '$F[34] =~ s/^0/+91/; print join ",",#F' test.csv
These command-line options are used:
-i edit the file in-place
-n loop around every line of the input file
-l removes newlines before processing, and adds them back in afterwards
-a autosplit mode – split input lines into the #F array. Defaults to splitting on whitespace.
-e execute the perl code
-F autosplit modifier, in this case splits on ,
#F is the array of words in each line, indexed starting with 0
$F[34] is the 35 element of the array
s/^0/+91/ does the substitution

Perl command is not behaving as expected?

I have a file with below contents:
[TEMP.s_m_update_BUS_spec]
$$SRC_STAT_RA=WHG_STATUS_SITEENTSEQCHAIN_20110901094550.dat
$InputFile_RA_SPE=/edwload/rqt/workingdir/status_spe/WHG_STATUS_SITEENTSEQCHAIN_20110901094550.dat
[TEMP.s_m_upd_salions_rqthk]
$$SRC_STAT_RN=WHG_STATUS_SITEENTSEQCHAIN_20110901094550
$InputFile_RN_RQT=/edwload/rqt/workingdir/restriction/WHG_STATUS_SITEENTSEQCHAIN_20110901094550.dat
I am using below perl command to just replace WHG_STATUS_SITEENTSEQCHAIN_20110901094550 with WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat in the section [TEMP.s_m_upd_salions_rqthk] But somehow its not giving me expected result. Even the WHG_STATUS_SITEENTSEQCHAIN_20110901094550 under section [TEMP.s_m_update_BUS_spec] is getting replaced.
perl -p -i -e "s|\$\$SRC_STAT_RN=.*|\$\$SRC_STAT_RN=WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat|g;s|\$InputFile_RN_RQT=\/edwload\/rqt\/workingdir\/restriction\/.*|\$InputFile_RN_RQT=\/edwload\/rqt\/workingdir\/restriction\/WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat|g" Input_File
Please let me know the modifications required in command above.Same subsitute commands works fine with SED command. But i wud want to use perl.
The program you run is
s|$$SRC_STAT_RN=.*|$$SRC_STAT_RN=WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat|g; s|$InputFile_RN_RQT=\/edwload\/rqt\/workingdir\/restriction\/.*|$InputFile_RN_RQT=\/edwload\/rqt\/workingdir\/restriction\/WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat|g
There are a fair number of $ that should be escaped but aren't. It would be simpler if you used single quotes instead of double quotes. You were probably trying for:
perl -i -pe'
s{\$\$SRC_STAT_RN=.*}{\$\$SRC_STAT_RN=WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat}g;
s{\$InputFile_RN_RQT=/edwload/rqt/workingdir/restriction/.*}{\$InputFile_RN_RQT=/edwload/rqt/workingdir/restriction/WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat}g;
' Input_File
What exactly is not working as you want? On my machine, after running your perl code, the file looks like:
[TEMP.s_m_update_BUS_spec] $$SRC_STAT_RA=WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat
[TEMP.s_m_upd_salions_rqthk] $$SRC_STAT_RN=WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat
Ain't this what you expected?
Edit
Try modifying your command to:
perl -p -i -e "s|\$\$SRC_STAT_RN=.*?|\$\$SRC_STAT_RN=WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat|gmx;s|\$InputFile_RN_RQT=/edwload/rqt/workingdir/restriction/.*?|\$InputFile_RN_RQT=/edwload/rqt/workingdir/restriction/WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat|gmx" Input_File
and see if the result is as expected:
[TEMP.s_m_update_BUS_spec]
$$SRC_STAT_RA=WHG_STATUS_SITEENTSEQCHAIN_20110901999999.datWHG_STATUS_SITEENTSEQCHAIN_20110901094550.dat
$InputFile_RA_SPE=WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat/edwload/rqt/workingdir/status_spe/WHG_STATUS_SITEENTSEQCHAIN_20110901094550.dat
[TEMP.s_m_upd_salions_rqthk]
$$SRC_STAT_RN=WHG_STATUS_SITEENTSEQCHAIN_20110901999999.datWHG_STATUS_SITEENTSEQCHAIN_20110901094550
$InputFile_RN_RQT=WHG_STATUS_SITEENTSEQCHAIN_20110901999999.dat/edwload/rqt/workingdir/restriction/WHG_STATUS_SITEENTSEQCHAIN_20110901094550.dat

ExtUtils::MakeMaker PERL_MM_OPT split on whitespace work-around

ExtUtils::MakeMaker splits PERL_MM_OPT on whitespace, which means that something like the following will not work.
export PERL_MM_OPT='LIBS="-L/usr/sfw/lib -lssl -lcrypto" INC=/usr/sfw/include'
Is there a known workaround for this, or will I have to avoid using PERL_MM_OPT in this scenario?
-- update --
mobrule came up with the excellent suggestion to use tabs instead of spaces. mobrule is right about it splitting on spaces only. However, the solution doesn't work because it looks like tabs are converted to spaces in environment variables.
> cat tmp.sh
export PERL_MM_OPT='LIBS="-L/usr/sfw/lib -lssl -lcrypto" INC=-I/usr/sfw/include'
echo $PERL_MM_OPT | perl -pe 's/\t/[t]/g' | perl -pe 's/ /[s]/g'
> head -1 tmp.sh | perl -pe 's/\t/[t]/g' | perl -pe 's/ /[s]/g'
export[s]PERL_MM_OPT='LIBS="-L/usr/sfw/lib[t]-lssl[t]-lcrypto"[s]INC=-I/usr/sfw/include'
> bash tmp.sh
LIBS="-L/usr/sfw/lib[s]-lssl[s]-lcrypto"[s]INC=-I/usr/sfw/include
-- update 2 --
So, the tab suggestion worked (I was misled by the behavior of echo, and came to the wrong conclusion as to why it failed,) but it doesn't solve the problem.
Now the problem is that ExtUtils/Liblist/Kid.pm isn't expecting a leading doublequote (same result happens with singlequote.)
Unrecognized argument in LIBS ignored: '"-L/usr/sfw/lib
So, it seems that the solution to this problem (if one exists) can't depend on quotes.
Actually, MakeMaker.pm splits on spaces but not on all whitespace. Could you use tabs?
export PERL_MM_OPT='LIBS="-L/usr/sfw/libTab-lsslTab-lcrypto" INC=/usr/sfw/include
I think you the way you have set environment variables with tabs is OK -- it is the echo command that is converting tabs to spaces:
$ VAR='abc^Idef'
$ echo $VAR | od -c
0000000 a b c d e f \n
0000010
That looks like it didn't work. But wait:
$ export VAR
$ perl -e 'print $ENV{VAR}' | od -c
0000000 a b c \t d e f
0000007
This still might or might not work in ExtUtils::MakeMaker depending on how the parameters in $ENV{PERL_MM_OPT} get passed to a subprocess (via system, exec, open |, etc.):
system("gcc helloworld.c -lssl\t-lcrypto\t-L/usr/sfw/lib") ### 1 ###
system("gcc", "helloworld.c", "-lssl\t-lcrypto\t-L/usr/sfw/lib") ### 2 ###
system call 1 will work because when the system call has one arg with any metacharacters, it passes the command to the shell. The shell will parse the arguments correctly.
system call 2 fails because multi-arg system always bypasses the shell and gcc is stuck looking for a library with the unlikely name of"libssl^I-lcrypto^I-L/usf/sfw/lib.a". If ExtUtils::MakeMaker is using this calling style to run the compiler, then this workaround won't get the job done.

perl one liner alternative to this bash "chain"?

I am trying to comprehend Perl following the way describe in the book "Minimal Perl".
I've uploaded all source txt files onto my own server : results folder
I got the output from using several bash commands in a "chain" like this:
cat run*.txt | grep '^Bank[[:space:]]Balance'|cut -d ':' -f2 | grep -E '\$[0-9]+'
I know this is far from the most concise and efficient, but at least it works...
As our uni subject now moves onto the Perl part, I'd like to know if there is a way to get the same results in one line?
I am trying something like the following code but stuck in the middle:
Chenxi Mao#chenxi-a6b123bb /cygdrive/c/eMarket/output
$ perl -wlne 'print; if $n=~/^Bank Balance/'
syntax error at -e line 1, near "if $n"
Execution of -e aborted due to compilation errors.
you shouldn't have a ; after the print. So
perl -wlne 'print $1 if $n=~/^Bank Balance\s*:\s*(\d+)/'
perl -F/\:/ -ane 'print $F[1]."\n" if /Bank Balance/ && $F[1]!~/\$-/' run*.txt
also here's a short version of your bash command, using just awk
awk -F": " '/Bank[ \t]*Balance/&& $2!~/\$-/{print $2}' run*.txt