Is there an equivalent of shell's "pwd -L" in perl?
I want the current working directory with symlinks unresolved?
My current working directory is "/path1/dir1/dir2/dir3", and here dir1 is symlink to test1/test2. I want current working directory to be "/path1/dir1/dir2/dir3" via perl script. What I am getting is /path1/test1/test2/dir2/dir3.
How can I get the current working directory to be the path with no symlinks resolved? In other words, I would want to implement shell's pwd -L.
use the perl backtick operator to run the pwd -L command on your system and capture the output into a variable, this works on my system:
perl -e 'chomp( my $pwdl = `pwd -L` ); print "$pwdl\n";'
An attempt to replicate the behavior of bash's pwd builtin using just perl (In particular, with the aid of the Path::Tiny and core Cwd modules):
First, from help pwd in a bash shell:
-L print the value of $PWD if it names the current working directory
-P print the physical directory, without any symbolic links
(The GNU coreutils version of pwd(1) also reads the PWD environment variable for its implementation of -L, which is why running it with qx// works even though it doesn't have access to the shell's internal variables keeping track of the working directory and path taken to it)
$ pwd -P # First, play with absolute path with symlinks resolved
/.../test1/test2/dir2/dir3
$ perl -MCwd -E 'say getcwd'
/.../test1/test2/dir2/dir3
$ perl -MPath::Tiny -E 'say Path::Tiny->cwd'
/.../test1/test2/dir2/dir3
$ pwd -L # Using $PWD to preserve the symlinks
/.../dir1/dir2/dir3
$ /bin/pwd -L
/.../dir1/dir2/dir3
$ PWD=/foo/bar /bin/pwd -L # Try to fake it out
/.../test1/test2/dir2/dir3
$ perl -MPath::Tiny -E 'my $pwd = path($ENV{PWD}); say $pwd if $pwd->realpath eq Path::Tiny->cwd'
/.../dir1/dir2/dir3
As a function (With some added checks so it can handle a missing $PWD environment var or one that points to a non-existent path):
#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;
use Path::Tiny;
sub is_same_file ($$) {
my $s1 = $_[0]->stat;
my $s2 = $_[1]->stat;
return $s1->dev == $s2->dev && $s1->ino == $s2->ino;
}
sub get_working_dir () {
my $cwd = Path::Tiny->cwd;
# $ENV{PWD} must exist and be non-empty
if (exists $ENV{PWD} && $ENV{PWD} ne "") {
my $pwd = path($ENV{PWD});
# And must point to a directory that is the same filesystem entity as cwd
return $pwd->is_dir && is_same_file($pwd, $cwd) ? $pwd : $cwd;
} else {
return $cwd;
}
}
say get_working_dir;
Related
How can I get the file name and input parameters of the script into a variable?
So it should look like this:
# Start script as such: ./myscript.pl -d -s server1.domain
#!/usr/bin/perl
use strict;
use warnings;
my $call = some_command;
print $call; # Output: myscript.pl -d -s server1.domain
# OR ./myscript.pl -d -s server1.domain
# OR /path/to/myscript.pl -d -s server1.domain
Tried doing this with __FILE__ and $0 but I can't seem to get the input parameters in the variable.
I'm running v5.10.1 on a AIX machine.
The program and its args are found in $0 and #ARGV respectively.
You can use String::ShellQuote's shell_quote to form a command line from them.
I found a slight misbehaviour in my Perl script when I create and check for the existence of directories with a tilde sign, which doesn't happen if I use a full /home/user path. When I run this script for the first time, it creates the new directory. When I run it the second time, it doesn't recognise the existence of the directory, and tries to create it a second time:
#!/usr/bin/perl
use strict;
my $outdir = '~/test';
my $cmd = "mkdir $outdir";
unless (-d $outdir) {
0 == system($cmd) or die "Error creating outdir $outdir\n $?";
}
1;
[~] $ rm test/ -rf
[~] $ perl dir.pl
[~] $ perl dir.pl
mkdir: cannot create directory `/home/avilella/test': File exists
Error creating outdir ~/test
256 at dir.pl line 7.
How can I reliably deal with directories that use the tilde ~ sign in Perl?
The tilde is interpreted by the shell to mean your home directory.
Hence Perl's -d operator sees something different (a file/directory called ~) to your shell invocation 'mkdir ~/whatever' (which expands ~ to mean /home/user).
I would try to use exclusively Perl functions to perform your operations. You'll avoid spawning new processes and your file access will be performed in a consistent fashion.
Note Perl's mkdir built-in function. Note also the File::Glob module which does perform expansion of the ~ character (perhaps useful if you have users entering directory names manually)
You can use the %ENV home directory, which is the values imported from the shell:
my $home = $ENV{HOME};
You should also know that mkdir is a Perl built-in function:
mkdir "$home/test" or die "Cannot create test: $!";
~ is interpreted by the shell that is invoked by the system function. It's the shell that replaces ~ by the user's home directory. As far as Perl or the kernel is concerned, ~ means a file or directory with a one-character name, like any other character. So the test done by -d fails, because there's no directory called ~.
If you'd used Perl's built-in mkdir function rather than calling an external command via a shell script, you would have had an error at that point because the directory ~ doesn't exist.
The user's home directory is almost always available in the environment variable HOME. If you like, you can fall back to querying the user database if HOME is not present, but that's an abnormal situation. Do use the HOME environment variable if it is present, because it is sometimes useful to change it to run a program with different configuration files, and the environment variable is always available in practice whereas the user database could be unavailable due to network trouble in some configurations (e.g. NIS or LDAP).
my $home_directory = $ENV{HOME};
if (!defined $home_directory) {$home_directory = getpwuid($<);}
my $outdir = "$home_directory/test";
unless (-d $outdir) {
mkdir $outdir or die "Error creating $outdir: $!\n"
}
Your script can't create directories which exist. That's the error you presented us:
[~] $ rm test/ -rf
[~] $ perl dir.pl
[~] $ perl dir.pl
mkdir: cannot create directory `/home/avilella/test': File exists
Error creating outdir ~/test
256 at dir.pl line 7.
The problem is the line of your delete:
[~] $ rm test/ -rf
is wrong. Like most commands, the right syntax would be:
[~] $ <command> <options> <parameters>
so it would be:
[~] $ rm -rf test/
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.
I want to call my perl script as a command from command line.
Example lets say I have a perl file like following
#!/usr/local/bin/perl -w
#args =("mvn","package");
system(#args) == 0
or die "system #args failed"
I right not call this using package.pl
I tried doing the following
#!/bin/sh
eval 'exec /bin/perl –x -S $0 ${1+"$#"}'
if 0;
#!/usr/local/bin/perl -w
#args =("mvn","package");
system(#args) == 0
or die "system #args failed"
and then name the file 'package' .Do chmod on package
When I try to run package, then I get the error
"Can't open perl script []x:No such file or directory
Can someone please point me out , as to how to do this properly??
Thanks
Neeraj
Change to the name you want to use and make it executable:
cp package.pl package
chmod +x package
Run it:
package
or:
./package
Changed single quotes to double quotes and escaped inner double quotes. Also, there seems to be some problem with paths. Try calling your script with absolute path. I tried adding "./" and it worked:
#!/bin/sh
echo "This is shell"
eval "exec /usr/bin/perl -x -S ./$0 ${1+\"$#\"}"
if 0;
#!/usr/bin/perl -w
print "This is Perl\n";
I'm not sure if these paths are duplicates. Given the relative path, how do I determine absolute path using a shell script?
Example:
relative path: /x/y/../../a/b/z/../c/d
absolute path: /a/b/c/d
The most reliable method I've come across in unix is readlink -f:
$ readlink -f /x/y/../../a/b/z/../c/d
/a/b/c/d
A couple caveats:
This also has the side-effect of resolving all symlinks. This may or may not be desirable, but usually is.
readlink will give a blank result if you reference a non-existant directory. If you want to support non-existant paths, use readlink -m instead. Unfortunately this option doesn't exist on versions of readlink released before ~2005.
From this source comes:
#!/bin/bash
# Assume parameter passed in is a relative path to a directory.
# For brevity, we won't do argument type or length checking.
ABS_PATH=`cd "$1"; pwd` # double quotes for paths that contain spaces etc...
echo "Absolute path: $ABS_PATH"
You can also do a Perl one-liner, e.g. using Cwd::abs_path
Using bash
# Directory
relative_dir="folder/subfolder/"
absolute_dir="$( cd "$relative_dir" && pwd )"
# File
relative_file="folder/subfolder/file"
absolute_file="$( cd "${relative_file%/*}" && pwd )"/"${relative_file##*/}"
${relative_file%/*} is same result as dirname "$relative_file"
${relative_file##*/} is same result as basename "$relative_file"
Caveats: Does not resolve symbolic links (i.e. does not canonicalize path ) => May not differentiate all duplicates if you use symbolic links.
Using realpath
Command realpath does the job. An alternative is to use readlink -e (or readlink -f). However realpath is not often installed by default. If you cannot be sure realpath or readlink is present, you can substitute it using perl (see below).
Using perl
Steven Kramer proposes a shell alias if realpath is not available in your system:
$ alias realpath="perl -MCwd -e 'print Cwd::realpath(\$ARGV[0]),qq<\n>'"
$ realpath path/folder/file
/home/user/absolute/path/folder/file
or if you prefer using directly perl:
$ perl -MCwd -e 'print Cwd::realpath($ARGV[0]),qq<\n>' path/folder/file
/home/user/absolute/path/folder/file
This one-line perl command uses Cwd::realpath. There are in fact three perl functions. They take a single argument and return the absolute pathname. Below details are from documentation Perl5 > Core modules > Cwd.
abs_path() uses the same algorithm as getcwd(). Symbolic links and relative-path components (. and ..) are resolved to return the canonical pathname, just like realpath.
use Cwd 'abs_path';
my $abs_path = abs_path($file);
realpath() is a synonym for abs_path()
use Cwd 'realpath';
my $abs_path = realpath($file);
fast_abs_path() is a more dangerous, but potentially faster version of abs_path()
use Cwd 'fast_abs_path';
my $abs_path = fast_abs_path($file);
These functions are exported only on request => therefore use Cwd to avoid the "Undefined subroutine" error as pointed out by arielf. If you want to import all these three functions, you can use a single use Cwd line:
use Cwd qw(abs_path realpath fast_abs_path);
Take a look at 'realpath'.
$ realpath
usage: realpath [-q] path [...]
$ realpath ../../../../../
/data/home
Since I've run into this many times over the years, and this time around I needed a pure bash portable version that I could use on OSX and linux, I went ahead and wrote one:
The living version lives here:
https://github.com/keen99/shell-functions/tree/master/resolve_path
but for the sake of SO, here's the current version (I feel it's well tested..but I'm open to feedback!)
Might not be difficult to make it work for plain bourne shell (sh), but I didn't try...I like $FUNCNAME too much. :)
#!/bin/bash
resolve_path() {
#I'm bash only, please!
# usage: resolve_path <a file or directory>
# follows symlinks and relative paths, returns a full real path
#
local owd="$PWD"
#echo "$FUNCNAME for $1" >&2
local opath="$1"
local npath=""
local obase=$(basename "$opath")
local odir=$(dirname "$opath")
if [[ -L "$opath" ]]
then
#it's a link.
#file or directory, we want to cd into it's dir
cd $odir
#then extract where the link points.
npath=$(readlink "$obase")
#have to -L BEFORE we -f, because -f includes -L :(
if [[ -L $npath ]]
then
#the link points to another symlink, so go follow that.
resolve_path "$npath"
#and finish out early, we're done.
return $?
#done
elif [[ -f $npath ]]
#the link points to a file.
then
#get the dir for the new file
nbase=$(basename $npath)
npath=$(dirname $npath)
cd "$npath"
ndir=$(pwd -P)
retval=0
#done
elif [[ -d $npath ]]
then
#the link points to a directory.
cd "$npath"
ndir=$(pwd -P)
retval=0
#done
else
echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
echo "opath [[ $opath ]]" >&2
echo "npath [[ $npath ]]" >&2
return 1
fi
else
if ! [[ -e "$opath" ]]
then
echo "$FUNCNAME: $opath: No such file or directory" >&2
return 1
#and break early
elif [[ -d "$opath" ]]
then
cd "$opath"
ndir=$(pwd -P)
retval=0
#done
elif [[ -f "$opath" ]]
then
cd $odir
ndir=$(pwd -P)
nbase=$(basename "$opath")
retval=0
#done
else
echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
echo "opath [[ $opath ]]" >&2
return 1
fi
fi
#now assemble our output
echo -n "$ndir"
if [[ "x${nbase:=}" != "x" ]]
then
echo "/$nbase"
else
echo
fi
#now return to where we were
cd "$owd"
return $retval
}
here's a classic example, thanks to brew:
%% ls -l `which mvn`
lrwxr-xr-x 1 draistrick 502 29 Dec 17 10:50 /usr/local/bin/mvn# -> ../Cellar/maven/3.2.3/bin/mvn
use this function and it will return the -real- path:
%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`
%% test.sh
relative symlinked path:
/usr/local/bin/mvn
and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn
I wanted to use realpath but it is not available on my system (macOS), so I came up with this script:
#!/bin/sh
# NAME
# absolute_path.sh -- convert relative path into absolute path
#
# SYNOPSYS
# absolute_path.sh ../relative/path/to/file
echo "$(cd $(dirname $1); pwd)/$(basename $1)"
Example:
./absolute_path.sh ../styles/academy-of-management-review.csl
/Users/doej/GitHub/styles/academy-of-management-review.csl
May be this helps:
$path = "~user/dir/../file"
$resolvedPath = glob($path); # (To resolve paths with '~')
# Since glob does not resolve relative path, we use abs_path
$absPath = abs_path($path);