executing a bash script in perl - perl

I want to run this command in perl
for dir in *; do
test -d "$dir" && ( find "$dir" -name '*test' | grep -q . || echo "$dir" );
done
I have tried :
system ("for dir in *; do
test -d "\$dir" && ( find "\$dir" -name '*test' | grep -q . || echo "\$dir" );
done");
but does not work .

A pure Perl implementation using File::Find module's find function:
#!/usr/bin/env perl
use strict;
use warnings;
use File::Find;
find \&find_directories, '.';
sub find_directories {
if ( -d && $File::Find::name =~ /test$/ ) {
print "$File::Find::name\n";
}
}

Your quoting is off.
"for dir in *; do
test -d "\$dir" && ( find "\$dir" -name '*test' | grep -q . || echo "\$dir" );
done"
You have decided to delimit your string with double quotes ", but they are included in your string.
Either escape the other quotes:
"for dir in *; do
test -d \"\$dir\" && ( find \"\$dir\" -name '*test' | grep -q . || echo \"\$dir\" );
done"
(error prone, ugly)
… or use another delimiter: Perl offers you a wide range of possibilities. These quoting syntaxes interpolate variables inside: "…" and qq{…} where you can use any character in [^\s\w] as delimiter, and non-interpolating syntaxes are: '…' and q{…} with the same delimiter flexibility as before:
qq{for dir in *; do
test -d "\$dir" && ( find "\$dir" -name '*test' | grep -q . || echo "\$dir" );
done}
The q and qq constructs can include the delimiter inside the string, if the occurrence is balanced: q( a ( b ) c ) works.
The third quoting mechanism is a here-doc:
system( <<END_OF_BASH_SCRIPT );
for dir in *; do
test -d "\$dir" && ( find "\$dir" -name '*test' | grep -q . || echo "\$dir" );
done
END_OF_BASH_SCRIPT
This is usefull for including longer fragments without worrying about a delimitor. The String is ended by a predefined token that has to appear on a line of its own. If the delimitor declaration is placed in single quotes (<<'END_OF_SCRIPT'), no variables will be interpolated:
system( <<'END_OF_BASH_SCRIPT' );
for dir in *; do
test -d "$dir" && ( find "$dir" -name '*test' | grep -q . || echo "$dir" );
done
END_OF_BASH_SCRIPT
Note on the q{} and qq{} syntax: This is a feature never to be used outside of obfuscation, but it is possible to use a character in \w as the delimiter. You have to include a space between the quoting operator q or qq and the delimiter. This works: q xabcx and is equal to 'abc'.

Instead of starting the script, try starting a bash instance that runs the script. E.g.
system("bash -c 'for dir bla bla bla'");

system() uses your default system shell, which is probably not Bash. The solution is to call Bash explicitly with the system() command.

Related

Bash or Python efficient substring matching and filtering

I have a set of filenames in a directory, some of which are likely to have identical substrings but not known in advance. This is a sorting exercise. I want to move the files with the maximum substring ordered letter match together in a subdirectory named with that number of letters and progress to the minimum match until no matches of 2 or more letters remain. Ignore extensions. Case insensitive. Ignore special characters.
Example.
AfricanElephant.jpg
elephant.jpg
grant.png
ant.png
el_gordo.tif
snowbell.png
Starting from maximum length matches to minimum length matches will result in:
./8/AfricanElephant.jpg and ./8/elephant.jpg
./3/grant.png and ./3/ant.png
./2/snowbell.png and ./2/el_gordo.tif
Completely lost on an efficient bash or python way to do what seems a complex sort.
I found some awk code which is almost there:
{
count=0
while ( match($0,/elephant/) ) {
count++
$0=substr($0,RSTART+1)
}
print count
}
where temp.txt contains a list of the files and is invoked as eg
awk -f test_match.awk temp.txt
Drawback is that a) this is hardwired to look for "elephant" as a string (I don't know how to make it take an input string (rather than file) and an input test string to count against, and
b) I really just want to call a bash function to do the sort as specified
If I had this I could wrap some bash script around this core awk to make it work.
function longest_common_substrings () {
shopt -s nocasematch
for file1 in * ; do for file in * ; do \
if [[ -f "$file1" ]]; then
if [[ -f "$file" ]]; then
base1=$(basename "$file" | cut -d. -f1)
base2=$(basename "$file1" | cut -d. -f1)
if [[ "$file" == "$file1" ]]; then
echo -n ""
else
echo -n "$file $file1 " ; $HOME/Scripts/longest_common_substring.sh "$base1" "$base2" | tr -d '\n' | wc -c | awk '{$1=$1;print}' ;
fi
fi
fi
done ;
done | sort -r -k3 | awk '{ print $1, $3 }' > /tmp/filesort_substring.txt
while IFS= read -r line; do \
file_to_move=$(echo "$line" | awk '{ print $1 }') ;
directory_to_move_to=$(echo "$line" | awk '{ print $2 }') ;
if [[ -f "$file_to_move" ]]; then
mkdir -p "$directory_to_move_to"
\gmv -b "$file_to_move" "$directory_to_move_to"
fi
done < /tmp/filesort_substring.txt
shopt -u nocasematch
where $HOME/Scripts/longest_common_substring.sh is
#!/bin/bash
shopt -s nocasematch
if ((${#1}>${#2})); then
long=$1 short=$2
else
long=$2 short=$1
fi
lshort=${#short}
score=0
for ((i=0;i<lshort-score;++i)); do
for ((l=score+1;l<=lshort-i;++l)); do
sub=${short:i:l}
[[ $long != *$sub* ]] && break
subfound=$sub score=$l
done
done
if ((score)); then
echo "$subfound"
fi
shopt -u nocasematch
Kudos to the original solution for computing the match in the script which I found elsewhere in this site

equivalent of shell script's grep command in perl

I have below syntax used in shell script-
imp_vol -u $undr_price -s $str_price -p $price -t $mat -c $iscall | grep "Black Scholes " | cut-d"=" -f2
Where imp_vol is an executable that prints something. What will be its equivalent in Perl script? For example:
imp_vol -u 110.5 -s 110.9 -p 0.005 -t 0.041 -c 1
Underlying Price = 110.5
Strike Price = 110.9
Price = 0.005
Time = 0.041
IsCall = 1
Black Scholes Vol = 0.0108141
So my purpose is to get the value of Black Scholes Vol in this case as `.0108141 in some variable,as I have to pass that variable in some function again.
Any help will be appreciated.
There is actually a grep function in perl. It takes an expression or a block as the first argument and a list of strings as the second argument. So you could do this:
my #list = grep(/abcd/, (<>));
See also: grep - perldoc.perl.org
In your specific case you can use the block form to extract the price like this:
imp_vol | perl -e 'print grep { s/\s+Black Scholes Vol = ([-+]?[0-9]*\.?[0-9]+)/$1/ } (<>)'
If you want all "Black Scholes " like your grep would match
imp_vol -u $undr_price -s $str_price -p $price -t $mat -c $iscall | perl -ne 'print $1 if $_ =~ /Black Scholes .* = (\d+(?:\.\d+)?)/;'
"Black Scholes Vol" exactly
| perl -ne 'print $1 if $_ =~ /Black Scholes Vol = (\d+(?:\.\d+)?)/;'
Use Regexes
while (<>) {
next if !/abcd/;
# ...
}
Also, to replace cut, use capture groups, but I can't provide more code because I don"t know your data format.
To execute the command, you can use open to get a filehandle from which you can read its output. You can then use a single regular expression to match the line and extract the value. For example:
my $cmd = "imp_vol -u $undr_price -s $str_price -p $price -t $mat -c $iscall";
open (my $imp, '-|', $cmd) or die "Couldn't execute command: $!";
my $extracted;
while (<$imp>) {
if (/Black Scholes Vol = (.*)/) {
$extracted = $1;
}
}
close $imp;
The parenthesis create a capture group which extracts the value into the special $1 variable.
If you're able to pipe input instead of having to execute the command within Perl, the following one-liner would suffice:
imp_vol ... | perl -ne 'print $1 if /Black Scholes Vol = (.*)/'

Resolve name by inode in current direcory

How can I resolve the name by the given inode in the current directory in the following script that prints all filenames of symlinks pointing to a specified file that is passed as an argument to the script. The list should be sorted by ctime.
#!/usr/bin/ksh
IFS="`printf '\n\t'`"
USAGE="usage: symlink.sh <file>"
get_ctime() {
perl -se 'use File::stat; $file=lstat($filename); print $file->ctime' -- -filename="$1"
}
stat_inode() {
perl -se 'use File::stat; $file=stat($filename); if (defined $file) { print $file->ino; }' -- -filename="$1"
}
lstat_inode() {
perl -se 'use File::stat; $file=lstat($filename); if (defined $file) { print $file->ino; }' -- -filename="$1"
}
if [ $# -eq 0 ]; then
echo "$USAGE"
exit 1
fi
FILE_NAME="$1"
FILE_INODE=$(stat_inode "$FILE_NAME")
if [ ! -e "$FILE_NAME" ]; then
echo "no such file \"$FILE_NAME\""
exit 1
fi
for LINK in ./* ./.[!.]* ;do
if [ -L "$LINK" ]; then
TARGET_INODE=$(stat_inode "$LINK")
if [ ! -z "$TARGET_INODE" ]; then
if [ "$FILE_INODE" -eq "$TARGET_INODE" ]; then
echo $(get_ctime "$LINK") $(lstat_inode "$LINK");
fi
fi
fi
done | sort -nk1 | awk '{print $2}'
Basically, I'd like to pipe awk to some kind of lookup function like this: | awk ' ' | lookup
I'd really appreciate if someone suggested a more elegant way to accomplish the task.
OS: SunOS 5.10
Shell: KSH
Something like this?
$ find . -maxdepth 1 -inum 2883399
./.jshintrc
$
or:
$ echo 2883399 | xargs -IX find . -maxdepth 1 -inum X
./.jshintrc
$

want to read file line by line and then want to cut the line on delimiter

cat $INPUT_FILE| while read LINE
do
abc=cut -d ',' -f 4 $LINE
Perl:
cat $INPUT_FILE | perl -ne '{my #fields = split /,/; print $fields[3];}'
The key is to use command substitution if you want the output of a command saved in a variable.
POSIX shell (sh):
while read -r LINE
do
abc=$(cut -d ',' -f 4 "$LINE")
done < "$INPUT_FILE"
If you're using a legacy Bourne shell, use backticks instead of the preferred $():
abc=`cut -d ',' -f 4 "$LINE"`
In some shells, you may not need to use an external utility.
Bash, ksh, zsh:
while read -r LINE
do
IFS=, read -r f1 f2 f3 abc remainder <<< "$LINE"
done < "$INPUT_FILE"
or
while read -r LINE
do
IFS=, read -r -a array <<< "$LINE"
abc=${array[3]}
done < "$INPUT_FILE"
or
saveIFS=$IFS
while read -r LINE
do
IFS=,
array=($LINE)
IFS=$saveIFS
abc=${array[3]}
done < "$INPUT_FILE"
Bash:
while read line ; do
cut -d, -f4 <<<"$line"
done < $INPUT_FILE
Straight Perl:
open (INPUT_FILE, "<$INPUT_FILE") or die ("Could not open $INPUT_FILE");
while (<INPUT_FILE>) {
#fields = split(/,/, $_);
$use_this_field_value = $fields[3];
# do something with field value here
}
close (INPUT_FILE);

perl + set parameter correctly in the syntax

I use the following find command to rename directories & files in Linux system
remark: see rename.pl script down below
find / -name 'host1' -print0 | xargs -0 /var/tmp/rename.pl 'print "changing $_\n"; s/host1/host_10/g'
in order to set parameter in place host1 name I set the following
OLD_HOST=host1
Example:
find / -name "$OLD_HOST" -print0 | xargs -0 /var/tmp/rename.pl 'print "changing $_\n"; s/$OLD_HOST/host_10/g'
the problem is that now after setting the $OLD_HOSTS (as s/$OLD_HOST/host_10/g' )
it doesn't replace host1 with host_10
my question: how to use correctly $OLD_HOST in the syntax in order to replace host1 with host_10 ?
#
rename.pl script:
#!/usr/bin/perl
#
# rename script examples from lwall:
# rename 's/\.orig$//' *.orig
# rename 'y/A-Z/a-z/ unless /^Make/' *
# rename '$_ .= ".bad"' *.f
# rename 'print "$_: "; s/foo/bar/ if <stdin> =~ /^y/i' *
$op = shift;
for (#ARGV) {
$was = $_;
eval $op;
die $# if $#;
rename($was,$_) unless $was eq $_;
}
It's not very clear what you are trying to do.
I guess you are setting
OLD_HOST=host1
in the shell and you are trying to get the value of $OLD_HOST env variable from a Perl script. If so you can make use of
$ENV{'OLD_HOST'}
as:
find / -name "$OLD_HOST" -print0 | xargs -0 /var/tmp/rename.pl 'print "changing $_\n"; s/$ENV{OLD_HOST}/host_10/g'
^^^^^^^^^^^^^^