I need to run a system command which would go to a directory and delete sub directories excluding files if present. I wrote the below command to perform this operation:
system("cd /home/faizan/test/cache ; for i in *\; do if [ -d \"$i\" ]\; then echo \$i fi done");
The command above keeps throwing syntax error. I have tried multiple combinations but still not clear how this should go. Please suggest.
Well, your command line does contain syntax errors. Try this:
system("cd /home/faizan/test/cache ; for i in *; do if [ -d \"\$i\" ]; then echo \$i; fi; done");
Or better yet, only loop over directories in the first place;
system("for i in /home/faizan/test/cache/*/.; do echo \$i; done");
Or better yet, do it without a loop:
system("echo /home/faizan/test/cache/*/.");
(I suppose you will want to rmdir instead of echo once it is properly debugged.)
Or better yet, do it all in Perl. There is nothing here which requires system().
You're still best off trying this as a bash command first. Formatting that properly makes it much clearer that you're missing statement terminators:
for i in *; do
if [ -d "$i" ]; then
echo $i
fi
done
And condensing that by replacing new lines with semicolons (apart from after do/then):
for i in *; do if [ -d "$i" ]; then echo $i; fi; done
Or as has been mentioned, just do it in Perl (I haven't tested this to the point of actually uncommenting remove_tree - be careful!):
use strict;
use warnings;
use File::Path 'remove_tree';
use feature 'say';
chdir '/tmp';
opendir my $cache, '.';
while (my $item = readdir($cache)) {
if ($item !~ /^\.\.?$/ && -d $item) {
say "Deleting '$item'...";
# remove_tree($item);
}
}
Using system
my #args = ("cd /home/faizan/test/cache ; for i in *; do if [ -d \"\$i\" ]; then echo \$i; fi; done");
system(#args);
Using Subroutine
sub do_stuff {
my #args = ( "bash", "-c", shift );
system(#args);
}
do_stuff("cd /home/faizan/test/cache ; for i in *; do if [ -d \"\$i\" ]; then echo \$i; fi; done");
As question title stand for system command, this will answer directly, but the sample command using bash contain only thing that will be simplier in perl only (take a look at other answer using opendir and -d in perl).
If you want to use system (instead of open $cmdHandle,"bash -c ... |"), the prefered syntax for execution commands like system or exec, is to let perl parsing the command line.
Try this (as you've already done):
perl -e 'system("bash -c \"echo hello world\"")'
hello world
perl -e 'system "bash -c \"echo hello world\"";'
hello world
And now better, same but letting perl ensure command line parsing, try this:
perl -e 'system "bash","-c","echo hello world";'
hello world
There are clearly 3 argument of system command:
bash
-c
the script
or little more:
perl -e 'system "bash","-c","echo hello world;date +\"Now it is %T\";";'
hello world
Now it is 11:43:44
as you can see in last purpose, there is no double double-quotes enclosing bash script part of command line.
**Nota: on command line, using perl -e '...' or perl -e "...", it's a little heavy to play with quotes and double-quotes. In a script, you could mix them:
system 'bash','-c','for ((i=10;i--;));do printf "Number: %2d\n" $i;done';
or even:
system 'bash','-c','for ((i=10;i--;));do'."\n".
'printf "Number: %2d\n" $i'."\n".
'done';
Using dots . for concatening part of (script part) string, there are always 3 arguments.
Related
What is the difference between the perl -n and perl -p options?
What is a simple example to demonstrate the difference?
How do you decide which one to use?
How do you decide which one to use?
You use -p if you want to automatically print the contents of $_ at the end of each iteration of the implied while loop. You use -n if you don't want to print $_ automatically.
An example of -p. Adding line numbers to a file:
$ perl -pe '$_ = "$.: $_"' your_file.txt
An example of -n. A basic grep replacement.
$ perl -ne 'print if /some search text/' your_file.txt
-p is short for -np, and it causes $_ to be printed for each pass of the loop created by -n.
perl -ne'...'
executes the following program:
LINE: while (<>) {
...
}
while
perl -pe'...'
executes the following program:
LINE: while (<>) {
...
}
continue {
die "-p destination: $!\n" unless print $_;
}
See perlrun for documentation about perl's command-line options.
What is the difference between the perl -n and perl -p options?
-p causes each line to be printed; equivalent to:
while (<>) { ... } continue { print }
-n does not automatically print each line; equivalent to:
while(<>) {...}
What is a simple example to demonstrate the difference?
e.g., replace foo with FOO:
$ echo 'foo bar' | perl -pe 's/foo/FOO/'
FOO bar
$ echo 'foo bar' | perl -ne 's/foo/FOO/'
$
How do you decide which one to use?
One example where -n is useful is when you don't want every line printed, and there is a conditional print in the code, e.g., only show lines containing foo:
$ echo -e 'foo\nbar\nanother foo' | perl -ne 'print if /foo/;'
foo
another foo
$
The command-line options are documented in perlrun documentation
perl -n is equivalent to while(<>){...}
perl -p is equivalent to while(<>){...;print;}
What is the equivalent Perl command to the GNU coreutils command readlink -f?
If any component of the file name except the last one is missing or unavailable, readlink produces no
output and exits with a nonzero exit code. A trailing slash is
ignored.
You can use Cwd:
use Cwd 'abs_path';
my $path = "/some/arbitrary/path";
print abs_path($path);
Test:
for q in exists imaginary imarginary/imaginary ; do
echo "$q"
echo -n "readlink -f: " ; readlink -f "$q"
echo -n "abs_path: " ; perl -MCwd=abs_path -E'say abs_path $ARGV[0]' "$q"
echo
done
Output:
exists
readlink -f: /home/eric/exists
abs_path: /home/eric/exists
imaginary
readlink -f: /home/eric/imaginary
abs_path: /home/eric/imaginary
imaginary/imaginary
readlink -f: abs_path:
As a total Perl rookie, I'm happy to say I have figured out this STDIN solution all by myself (after several tries, remember that Perl's learning curve IS known to be steep).
devnull's solution was great with no doubt, but it was a little too "scriptish" for my taste - whereas I'd sometimes just want to pipe a perl one-liner to an echo'ed string, like this:
echo "/home/user/somesymlinkedpath" | perl -MCwd=abs_path -nle 'print abs_path $_'
So as there might be more people around who want to know about how to code this kind of piped form (making perl read the argument from STDIN), I've decided to post it here too.
I am calling many Perl scripts in my Bash script (sometimes from csh also).
At the start of the Bash script I want to put a test which checks if all the Perl scripts are devoid of any compilation errors.
One way of doing this would be to actually call the Perl script from the Bash script and grep for "compilation error" in the piped log file, but this becomes messy as different Perl scripts are called at different points in the code, so I want to do this at the very start of the Bash script.
Is there a way to check if the Perl script has no compilation error?
Beware!!
Using the below command to check compilation errors in your Perl program can be dangerous.
$ perl -c yourperlprogram
Randal has written a very nice article on this topic which you should check out
Sanity-checking your Perl code (Linux Magazine Column 91, Mar 2007)
Quoting from his article:
Probably the simplest thing we can tell is "is it valid?". For this,
we invoke perl itself, passing the compile-only switch:
perl -c ourprogram
For this operation, perl compiles the program,
but stops just short of the execution phase. This means that every
part of the program text is translated into the internal data
structure that represents the working program, but we haven't actually
executed any code. If there are any syntax errors, we're informed, and
the compilation aborts.
Actually, that's a bit of a lie. Thanks to BEGIN blocks (including
their layered-on cousin, the use directive), some Perl code may have
been executed during this theoretically safe "syntax check". For
example, if your code contains:
BEGIN { warn "Hello, world!\n" }
then you will see that message,
even during perl -c! This is somewhat surprising to people who
consider "compile only" to mean "executes no code". Consider the
code that contains:
BEGIN { system "rm", "-rf", "/" }
and you'll see the problem with
that argument. Oops.
Apart from perl -c program.pl, it's also better to find warnings using the command:
perl -w program.pl
For details see: http://www.perl.com/pub/2004/08/09/commandline.html
I use the following part of a bash func for larger perl projects :
# foreach perl app in the src/perl dir
while read -r dir ; do
echo -e "\n"
echo "start compiling $dir ..." ;
cd $product_instance_dir/src/perl/$dir ;
# run the autoloader utility
find . -name '*.pm' -exec perl -MAutoSplit -e 'autosplit($ARGV[0], $ARGV[1], 0, 1, 1)' {} \;
# foreach perl file check the syntax by setting the correct INC dirs
while read -r file ; do
perl -MCarp::Always -I `pwd` -I `pwd`/lib -wc "$file"
# run the perltidy inline
# perltidy -b "$file"
# sleep 3
ret=$? ;
test $ret -ne 0 && break 2 ;
done < <(find "." -type f \( -name "*.pl" -or -name "*.pm" \))
test $ret -ne 0 && break ;
echo "stop compiling $dir ..." ;
echo -e "\n\n"
cd $product_instance_dir ;
done < <(ls -1 "src/perl")
When you need to check errors/warnings before running but your file depends on mutliple other files you can add option -I:
perl -I /path/to/dependency/lib -c /path/to/file/to/check
Edit: from man perlrun
Directories specified by -I are prepended to the search path for modules (#INC).
I am using Perl to search and replace multiple regular expressions:
When I execute the following command, I get an error:
prompt> find "*.cpp" | xargs perl -i -pe 's/##(\W)/\1/g' -pe 's/(\W)##/\1/g'
syntax error at -e line 2, near "s/(\W)##/\1/g"
Execution of -e aborted due to compilation errors.
xargs: perl: exited with status 255; aborting
Having multiple -e is valid in Perl, then why is this not working? Is there a solution to this?
Several -e's are allowed.
You are missing the ';'
find "*.cpp" | xargs perl -i -pe 's/##(\W)/\1/g;' -pe 's/(\W)##/\1/g;'
Perl statements has to end with ;.
Final statement in a block doesn't need a terminating semicolon.
So a single -e without ; will work, but you will have to add ; when you have multiple -e statements.
Having multiple -e values are valid, but is it useful? The values from the multiple -e are merely combined into one program, and it's up to you to ensure that together they make a syntactically correct program. The B::Deparse program can show you what perl thinks the program is:
$ perl -MO=Deparse -e 'print' -e 'q(Hello' -e ')'
print "Hello\n";
-e syntax OK
A curious thing to note is that a newline snuck in there. Think about how it got there to see what else perl is doing to combine multiple -e values.
In your program, you are substituting on the current line, then taking the modified line and substituting again. That's better written as:
prompt> find "*.cpp" | xargs perl -i -pe 's/##(\W)/\1/g; s/(\W)##/\1/g'
Now, if you are building up this command line by adding more and more -e through some automated process and you don't know ahead of time what you get, maybe those -e make sense. However, you might consider that you can do the same thing to build up the string you give to -e. I don't know what might be better because you didn't explain why you are doing it that way.
But, I suspect that in some cases, people are actually thinking about having only one substitution work. They want to try one and if its pattern doesn't work, try a different one until one succeeds. In that case you don't want to separate the substitutions by semicolons. Use the short-circuiting || instead. The s/// returns the number of substitutions it made and || will stop (short circuit) when it finds a true value:
prompt> find "*.cpp" | xargs perl -i -pe 's/##(\W)/\1/g || s/(\W)##/\1/g'
And note, you only need one -p. It only does its job once. Here's the program with multiple -p deparsed:
$ perl -MO=Deparse -i -pe 's/##(\W)/\1/g;' -pe 's/(\W)##/\1/g;'
BEGIN { $^I = ""; }
LINE: while (defined($_ = readline ARGV)) {
s/##(\W)/$1/g;
s/(\W)##/$1/g;
}
continue {
die "-p destination: $!\n" unless print $_;
}
-e syntax OK
It's the same thing as having only one -p:
$ perl -MO=Deparse -pi -e 's/##(\W)/\1/g;' -e 's/(\W)##/\1/g;'
BEGIN { $^I = ""; }
LINE: while (defined($_ = readline ARGV)) {
s/##(\W)/$1/g;
s/(\W)##/$1/g;
}
continue {
die "-p destination: $!\n" unless print $_;
}
-e syntax OK
Thanks so much! You helped me reduce my ascii / decimal / 8-bit binary table printer enough to fit in a tweet:
for i in {32..126};do printf "'\x$(printf %x $i)'(%3i) = " $i; printf '%03o\n' $i | perl \
-pe 's#0#000#g;' -pe 's#1#001#g;' -pe 's#2#010#g;' -pe 's#3#011#g;' \
-pe 's#4#100#g;' -pe 's#5#101#g;' -pe 's#6#110#g;' -pe 's#7#111#g' ; done | \
perl -pe 's#= 0#= #'
I have scrapped a perl snippet off the web for use in my bash scrip and for reasons too long to go into, it will be better if I could achieve what it tries to do directly in bash.
Here is the script:
bash stuff
...
perl <<'EOF'
use 5.006;
use strict;
use warnings;
if (! can_run("ldconfig")) {
die "you need to have ldconfig in your PATH env to proceed.\n";
}
# check if we can run some command
sub can_run {
my ($cmd) = #_;
#warn "can run: #_\n";
my $_cmd = $cmd;
return $_cmd if -x $_cmd;
return undef;
EOF
more bash stuff
Basically, the question could be rephrased as , "how can I check if ldconfig is in the PATH env using bash?"
You want bash's builtin type command:
if type -P ldconfig; then
echo "ldconfig is in the PATH"
else
echo "ldconfig is not in the PATH"
fi
Expressed negatively:
if ! type -P ldconfig; then
echo "ldconfig is not in the PATH"
fi
A more straightforward solution would be to invoke the shell and the which command:
$path = `which ldconfig`;
if ($path) {
...
}
If ldconfig is recognised, the path to its executable will be returned, empty output otherwise.
Or, if this Perl script is not going to do anything more than that, you can dismiss it and execute the same command from bash.
I refined #glenn jackman's answer to make it "quiet". It worked as it was but it output "/sbin/ldconfig" to the screen in addition to the echo when in the path. With this modification, only the echo is output:
type ldconfig &>/dev/null
if [ "$?" -eq 0 ]
then
echo "ldconfig is in the PATH"
else
echo "ldconfig is not in the PATH"
fi
Thanks to all.