I am passing in the full path to a file as a commandline argument in perl.
For example
myscript.pl C:\Dir\myfile.txt
In myscript.pl, I have
my $full_path = shift;
print $full_path;
When I do this, my output is
C:Dirmyfile.txt
What I really want is C:\Dir\myfile.txt
But when I run my script as
myscript.pl 'C:\Dir\myfile.txt'
my output is C:/Dir/myfile.txt. Now it has forward slashes instead of backslashes. How do I get what I want? (The same text as what was passed in, file path with backslashes)
I need to run be able to run this script on Cygwin in a windows environment. Note that the script serves a larger purpose, but what I have posted is the part I am stuck with. The path is something I copy from somewhere else, so I really don't want to do the extra work of replacing backslash with forward slash or spaces.
use the File::Spec module. This simplifies passing parameters to your script, since you don't need to use slashes, and it also makes your application portable across operating systems.
use File::Spec;
my $full_path = File::Spec->catfile(#ARGV);
print $full_path, "\n";
Example:
perl myscript.pl C: Dir myfile.txt
C:\Dir\myfile.txt
Alternatively, if you need to use the full path string, then use the following line in place of the above:
my $full_path = File::Spec->canonpath($ARGV[0]);
Example 2:
perl myscript.pl C:\Dir\myfile.txt
--OR--
perl myscript.pl C:/Dir/myfile.txt
C:\Dir\myfile.txt
Example 3 (for Cygwin) - surround parameter with single quotes:
perl myscript.pl 'C:\Dir\myfile.txt'
C:\Dir\myfile.txt
Related
cat ~/.last_dir
/mnt/c/Users/Administrator/OneDrive/Desktop/main project/backup/main project 2
cd cat ~/.last_dir
-bash: cd: too many arguments
I tried using backslash inside the file
/mnt/c/Users/Administrator/OneDrive/Desktop/main\ project/backup/main\ project\ 2
Still same error
You need to quote the results of expanding cat ...:
cd "$(cat ~/.last_dir)"
cd "$(<~/.last_dir)"
First, put quotes around the $(...) to make the space part of the filename.
Second, $(<...) is a bash construct that reads the file directly without executing cat, but is not entirely portable.
For a more generic, less bash-specific version, use Maxim's solution.
just put quotation marks around your path:
"/mnt/c/Users/Administrator/OneDrive/Desktop/main project/backup/main project 2"
this should work for most cases
This question already has answers here:
How can I store a command in a variable in a shell script?
(12 answers)
Closed 4 years ago.
These work as advertised:
grep -ir 'hello world' .
grep -ir hello\ world .
These don't:
argumentString1="-ir 'hello world'"
argumentString2="-ir hello\\ world"
grep $argumentString1 .
grep $argumentString2 .
Despite 'hello world' being enclosed by quotes in the second example, grep interprets 'hello (and hello\) as one argument and world' (and world) as another, which means that, in this case, 'hello will be the search pattern and world' will be the search path.
Again, this only happens when the arguments are expanded from the argumentString variables. grep properly interprets 'hello world' (and hello\ world) as a single argument in the first example.
Can anyone explain why this is? Is there a proper way to expand a string variable that will preserve the syntax of each character such that it is correctly interpreted by shell commands?
Why
When the string is expanded, it is split into words, but it is not re-evaluated to find special characters such as quotes or dollar signs or ... This is the way the shell has 'always' behaved, since the Bourne shell back in 1978 or thereabouts.
Fix
In bash, use an array to hold the arguments:
argumentArray=(-ir 'hello world')
grep "${argumentArray[#]}" .
Or, if brave/foolhardy, use eval:
argumentString="-ir 'hello world'"
eval "grep $argumentString ."
On the other hand, discretion is often the better part of valour, and working with eval is a place where discretion is better than bravery. If you are not completely in control of the string that is eval'd (if there's any user input in the command string that has not been rigorously validated), then you are opening yourself to potentially serious problems.
Note that the sequence of expansions for Bash is described in Shell Expansions in the GNU Bash manual. Note in particular sections 3.5.3 Shell Parameter Expansion, 3.5.7 Word Splitting, and 3.5.9 Quote Removal.
When you put quote characters into variables, they just become plain literals (see http://mywiki.wooledge.org/BashFAQ/050; thanks #tripleee for pointing out this link)
Instead, try using an array to pass your arguments:
argumentString=(-ir 'hello world')
grep "${argumentString[#]}" .
In looking at this and related questions, I'm surprised that no one brought up using an explicit subshell. For bash, and other modern shells, you can execute a command line explicitly. In bash, it requires the -c option.
argumentString="-ir 'hello world'"
bash -c "grep $argumentString ."
Works exactly as original questioner desired. There are two restrictions to this technique:
You can only use single quotes within the command or argument strings.
Only exported environment variables will be available to the command
Also, this technique handles redirection and piping, and other shellisms work as well. You also can use bash internal commands as well as any other command that works at the command line, because you are essentially asking a subshell bash to interpret it directly as a command line. Here's a more complex example, a somewhat gratuitously complex ls -l variant.
cmd="prefix=`pwd` && ls | xargs -n 1 echo \'In $prefix:\'"
bash -c "$cmd"
I have built command processors both this way and with parameter arrays. Generally, this way is much easier to write and debug, and it's trivial to echo the command you are executing. OTOH, param arrays work nicely when you really do have abstract arrays of parameters, as opposed to just wanting a simple command variant.
I have a BASH script that has a long set of arguments and two ways of calling it:
my_script --option1 value --option2 value ... etc
or
my_script val1 val2 val3 ..... valn
This script in turn compiles and runs a large FORTRAN code suite that eventually produces a netcdf file as output. I already have all the metadata in the netcdf output global attributes, but it would be really nice to also include the full run command one used to create that experiment. Thus another user who receives the netcdf file could simply reenter the run command to rerun the experiment, without having to piece together all the options.
So that is a long way of saying, in my BASH script, how do I get the last command entered from the parent shell and put it in a variable? i.e. the script is asking "how was I called?"
I could try to piece it together from the option list, but the very long option list and two interface methods would make this long and arduous, and I am sure there is a simple way.
I found this helpful page:
BASH: echoing the last command run
but this only seems to work to get the last command executed within the script itself. The asker also refers to use of history, but the answers seem to imply that the history will only contain the command after the programme has completed.
Many thanks if any of you have any idea.
You can try the following:
myInvocation="$(printf %q "$BASH_SOURCE")$((($#)) && printf ' %q' "$#")"
$BASH_SOURCE refers to the running script (as invoked), and $# is the array of arguments; (($#)) && ensures that the following printf command is only executed if at least 1 argument was passed; printf %q is explained below.
While this won't always be a verbatim copy of your command line, it'll be equivalent - the string you get is reusable as a shell command.
chepner points out in a comment that this approach will only capture what the original arguments were ultimately expanded to:
For instance, if the original command was my_script $USER "$(date +%s)", $myInvocation will not reflect these arguments as-is, but will rather contain what the shell expanded them to; e.g., my_script jdoe 1460644812
chepner also points that out that getting the actual raw command line as received by the parent process will be (next to) impossible. Do tell me if you know of a way.
However, if you're prepared to ask users to do extra work when invoking your script or you can get them to invoke your script through an alias you define - which is obviously tricky - there is a solution; see bottom.
Note that use of printf %q is crucial to preserving the boundaries between arguments - if your original arguments had embedded spaces, something like $0 $* would result in a different command.
printf %q also protects against other shell metacharacters (e.g., |) embedded in arguments.
printf %q quotes the given argument for reuse as a single argument in a shell command, applying the necessary quoting; e.g.:
$ printf %q 'a |b'
a\ \|b
a\ \|b is equivalent to single-quoted string 'a |b' from the shell's perspective, but this example shows how the resulting representation is not necessarily the same as the input representation.
Incidentally, ksh and zsh also support printf %q, and ksh actually outputs 'a |b' in this case.
If you're prepared to modify how your script is invoked, you can pass $BASH_COMMANDas an extra argument: $BASH_COMMAND contains the raw[1]
command line of the currently executing command.
For simplicity of processing inside the script, pass it as the first argument (note that the double quotes are required to preserve the value as a single argument):
my_script "$BASH_COMMAND" --option1 value --option2
Inside your script:
# The *first* argument is what "$BASH_COMMAND" expanded to,
# i.e., the entire (alias-expanded) command line.
myInvocation=$1 # Save the command line in a variable...
shift # ... and remove it from "$#".
# Now process "$#", as you normally would.
Unfortunately, there are only two options when it comes to ensuring that your script is invoked this way, and they're both suboptimal:
The end user has to invoke the script this way - which is obviously tricky and fragile (you could however, check in your script whether the first argument contains the script name and error out, if not).
Alternatively, provide an alias that wraps the passing of $BASH_COMMAND as follows:
alias my_script='/path/to/my_script "$BASH_COMMAND"'
The tricky part is that this alias must be defined in all end users' shell initialization files to ensure that it's available.
Also, inside your script, you'd have to do extra work to re-transform the alias-expanded version of the command line into its aliased form:
# The *first* argument is what "$BASH_COMMAND" expanded to,
# i.e., the entire (alias-expanded) command line.
# Here we also re-transform the alias-expanded command line to
# its original aliased form, by replacing everything up to and including
# "$BASH_COMMMAND" with the alias name.
myInvocation=$(sed 's/^.* "\$BASH_COMMAND"/my_script/' <<<"$1")
shift # Remove the first argument from "$#".
# Now process "$#", as you normally would.
Sadly, wrapping the invocation via a script or function is not an option, because the $BASH_COMMAND truly only ever reports the current command's command line, which in the case of a script or function wrapper would be the line inside that wrapper.
[1] The only thing that gets expanded are aliases, so if you invoked your script via an alias, you'll still see the underlying script in $BASH_COMMAND, but that's generally desirable, given that aliases are user-specific.
All other arguments and even input/output redirections, including process substitutiions <(...) are reflected as-is.
"$0" contains the script's name, "$#" contains the parameters.
Do you mean something like echo $0 $*?
I am trying to run an SSH command that will invoke a script on a remote machine that writes some Lua code to a file.
I have this script command that executes under bash:
ssh bob writelua.sh '{version=1,{["foo"]=17}}'
And writelua.sh looks like this:
echo "return $1" > bar.lua
The end result, however, is that bar.lua has the content:
return version=1
I had thought that single quotes prevented all interpretation. How can I edit the scripts and escaping to pass the raw Lua code through unharmed?
The single quotes prevent interpretation on the local host. The remote host sees the command line
writelua.sh {version=1,{["foo"]=17}}
which is subject to brace expansion. You need a second set of quotes so that the first set of single quotes is passed through to the remote host.
ssh bob writelua.sh "'{version=1,{[\"foo\"]=17}}'"
As you can see, the quotes start to get unwieldy. A better solution is to simply copy a script containing
writelua.sh '{version=1,{["foo"]=17}}'
to the remote host and execute that remotely.
An example using the $'...' quotes:
ssh bob writelua.sh $'{version=1,{[\'foo\']=17}}'
Use heredoc and avoid all the excessive quoting:
ssh -T bob << \EOF
writelua.sh '{version=1,{["foo"]=17}}'
EOF
This will send raw script to remote host and it will get interpreted on the remote host itself.
When it gets too complex, particularly with lots of escaping, I prefer generating the command on a temporary script and execute it locally or remotely via SSH as required.
But there's an alternative: using echo to store the command in a variable and taking advantage of three things:
Single quotes don't do variable expansion and allow double quotes, so you can include something like "$myvar" without escaping $ or "
Double quotes allow variable expansion and single quotes, which means you can include something like animals='all'; echo love $animals to have $animals replaced by its value, and without escaping the '
Strings of both types, i.e. enclosed by single quotes or double quotes, can be concatenated simply by putting them together.
As an example, if I want something like this executed on a remote machine:
source /my-env.sh; perl -MMYLIB::DB -e 'my $t=db_list("name", 1553786458); print "#$t"'
But instead of 1553786458 I want to pass the value from a local variable:
now=`date +%s`
We could have this:
get_list=`echo 'source /my-env.sh; perl -MMYLIB::DB -e' "'my " '$t=db_list("name", ' "$now" '); print "#$t"' "'"`
You can see that single and double quotes are alternated, so we din't have to do any escaping! They don't need to be separated by spaces, but it improves readability and won't affect the result in this case.
And now we can execute:
ssh user#host $get_list
There's still no guarantee that this approach will always work, so once you've built your command, the safest bet would be to copy it over in a file.
If you can use Perl...
use Net::OpenSSH;
my $ssh = Net::OpenSSH->new("bob");
$ssh->system('writelua.sh', '{version=1,{["foo"]=17}}')
or die $ssh->error;
Net::OpenSSH takes care of quoting everything for you.
Code
#!/usr/bin/perl -I/root/Lib/
use Data::Dumper;
print Dumper \#INC;
The above code file name is test.pl and the permission is 755.
When I am running the program using /usr/bin/perl test.pl the output of the #INC contains "/root/Lib" at the end. It is like push in to #INC.
/usr/bin/perl test.pl Output
$VAR1 = [
'/etc/perl',
'/usr/local/lib/perl/5.10.0',
'/usr/local/share/perl/5.10.0',
'/usr/lib/perl5',
'/usr/share/perl5',
'/usr/lib/perl/5.10',
'/usr/share/perl/5.10',
'/usr/local/lib/site_perl',
'.',
'/root/Lib/'
];
But when I am running the program using ./test.pl the output of the #INC contain "/root/Lib/" contain first as well as end also. It is like unshift and push.
./test.pl output
$VAR1 = [
'/root/Lib/',
'/etc/perl',
'/usr/local/lib/perl/5.10.0',
'/usr/local/share/perl/5.10.0',
'/usr/lib/perl5',
'/usr/share/perl5',
'/usr/lib/perl/5.10',
'/usr/share/perl/5.10',
'/usr/local/lib/site_perl',
'.',
'/root/Lib/'
];
So I want to know what is the difference between ./test.pl and /usr/bin/perl test.pl ?
There are two questions lurking here. The headline question is "What's the difference between ./test.pl and perl test.pl?", while the secondary question is "Why is /root/Lib added at the front of #INC when the script is run as ./test.pl and not when run as perl test.pl?"
An answer, not necessarily applicable to your situation, is that ./test.pl runs the Perl interpreter specified by the shebang (/usr/bin/perl), whereas perl test.pl runs whatever Perl interpreter is found first on your $PATH (or aliases or functions). These need not be the same version of Perl. For me, they very seldom are the same version of Perl; the one in /usr/bin is usually relatively old and the one on my $PATH is relatively new (5.8.x vs 5.18.x, for example).
Working with Perl 5.12.4 (ouch; that's old) from /usr/bin on my machine, and using your script, I see:
$ perl test.pl
$VAR1 = [
'/root/Lib/',
'/Library/Perl/5.12/darwin-thread-multi-2level',
'/Library/Perl/5.12',
'/Network/Library/Perl/5.12/darwin-thread-multi-2level',
'/Network/Library/Perl/5.12',
'/Library/Perl/Updates/5.12.4',
'/System/Library/Perl/5.12/darwin-thread-multi-2level',
'/System/Library/Perl/5.12',
'/System/Library/Perl/Extras/5.12/darwin-thread-multi-2level',
'/System/Library/Perl/Extras/5.12',
'.'
];
$ ./test.pl
$VAR1 = [
'/root/Lib/',
'/root/Lib/',
'/Library/Perl/5.12/darwin-thread-multi-2level',
'/Library/Perl/5.12',
'/Network/Library/Perl/5.12/darwin-thread-multi-2level',
'/Network/Library/Perl/5.12',
'/Library/Perl/Updates/5.12.4',
'/System/Library/Perl/5.12/darwin-thread-multi-2level',
'/System/Library/Perl/5.12',
'/System/Library/Perl/Extras/5.12/darwin-thread-multi-2level',
'/System/Library/Perl/Extras/5.12',
'.'
];
$
Note that here the /root/Lib name is added once or twice to #INC. My best guess is that when you use perl test.pl, Perl scans the shebang and adds the -I option if finds there. When you use ./test.pl, the kernel runs /usr/bin/perl -I/root/Lib test.pl (where we can negotiation on whether the name test.pl appears on the command line; the key point is that the -I/root/Lib does appear), so Perl adds /root/Lib once because of the explicit -I provided by the kernel, and then adds another because it parses the shebang line.
See: perldoc perlrun for many more details.
You should use lib instead. That way, execution is consistent.
#!/usr/bin/perl
use lib qw( /root/Lib/ );
use Data::Dumper;
print Dumper \#INC;
See http://perldoc.perl.org/lib.html
When you run it as perl test.pl, among the first things it does is look to see if there is a line starting with #!. If it finds one it tries to act as if it were called with those arguments.
So the following adds warnings
#! perl -w
If you run it as ./test.pl your system actually runs it with those arguments. Perl really doesn't have a way of knowing that it was called implicitly like that. So Perl just parses that line itself like it did previously.
In your case that means that /root/Lib/ will be added to #INC twice.
The reason it appears at the beginning of the list; is that when Perl is actually called with that option, it adds it before it has a chance to load '#INC with anything.
If it gets added when parsing #!, it has already populated #INC so it adds it at the end.