Getting the ruby exec syntax right - ruby

I want to execute a variable shell command through ruby. I am a ruby beginner. I had the command working without variables, but when I tried to set variables the exec command doesn't work and the script stops running.
All 3 variables hold Strings.
The print is working fine, but exec not.
print "#{jobserv}" + options[:machines] + " -c touch #{noautooffPath} \n"
exec('"#{jobserv}" + options[:machines] + " -c touch #{noautooffPath}"')
What do i have to do to make the exec right or to get more hints by the system on how to correct this?
jobserv hold a path to an executable file plus options, options[:machines] holds a parameter I give to the last option flag of jobserv.

First, check you want exec. Several other options, like system, are more common choices for running a shell command. (Specifically, if exec is successful, the Ruby script will stop executing and be replaced by the command it executes; the rest of the script will never be run.)
You seem to have an extra layer of single quotes in your exec call. You probably want something closer to this:
exec("#{jobserv}" + options[:machines] + " -c touch #{noautooffPath}")
(assuming the print is showing the value you want)
In general, it's safer to avoid the single-string shell form, and pass all the argument individually. That might look more like this:
exec("#{jobserv}#{options[:machines]}", "-c", "touch", noautooffPath)
or:
exec(jobserv, options[:machines], "-c", "touch", noautooffPath)
(depending on whether jobserv and options[:machines] are expected to combine. And it'd look different again if any of those are expected to themselves contain a full multi-argument command invocation.)

Related

Using perl system() query with spaces in path variable

I seem to be stuck here. I want to send a system request from my script to another server via SSH, checking if a folder there exists. A folder path is passed from another script, stored in a variable and might have a space character in it. Since I couldn't replace the space with another character, to avoid a "not found" on folder like "foo bar", I need to pass something like
ls '/folderpath/foo bar' to other server's shell.
Sample code looks like this:
$cmd = 'ssh -i id_pub $ssh_addr ls $remote_dir';
if (system($cmd) == 0) {
do something
}
I've exhausted all possible options - tired to escape the possible space with \ before passing it to the command, tried to pass it with ' ', " ", inside and adding both before passing it into $cmd.
But I always end up with something like this:
ls \folderpath\foo\\ bar or ls \' \folderpath\foo bar\'
but not ls '\folderpath\foo bar'
I'm not that good with Perl, possible someone more experienced can recommend a workaround?
String::ShellQuote's shell_quote is useful in building shell commands.
my $remote_cmd = shell_quote("ls", "--", $remote_dir);
my $local_cmd = shell_quote("ssh", "-i", "id_pub", $ssh_addr, $remote_cmd);
system($local_cmd);
Of course, you can avoid the shell on the local side as follows:
use String::ShellQuote qw( shell_quote );
my $remote_cmd = shell_quote("ls", "--", $remote_dir);
system("ssh", "-i", "id_pub", $ssh_addr, $remote_cmd);
Running a local shell and using it to escape your command to be safe for the remote shell would look like this:
system('env', "ssh_addr=$ssh_addr", "remote_dir=$remote_dir", 'bash', '-c',
'printf -v remote_cmd "%q " ls -- "$remote_dir"; ssh "$ssh_addr" "$remote_cmd"');
Unlike just using "'$remote_cmd'", the above works with all possible values, including intentionally malicious ones, so long as your remote shell is also bash.
Thanks to #ikegami's answer for demonstrating the use of the end-of-options sigil -- to ensure that even a remote_dir value that starts with dashes is parsed as a positional argument by ls
OK you have several possibilities for shell expansion with the way you are doing this.
Firstly is using system() with a string. This will break all your paths on the space characters. you can solve this by using system as a list
system('ssh', '-i', 'id_pub', $ssh_addr, 'ls', $remote_dir)
Now we still have a problem as ssh will run the remote code on the remote server in a shell with shell expansion which will break the path on spaces again
So you need to put $remote_dir inside ' characters to stop the remote shell from breaking up the path: giving
system('ssh', '-i', 'id_pub', $ssh_addr, 'ls', "'$remote_dir'")
Hope this helps/works
Note that as the commenters below have said this makes the assumption that $remote_dir has no ' characters in it. You need to be either escaping or parsing $remote_dir to ensure that you don't get a path that looks like /file.txt'; rm -rf / # which will attempt to remove every file on the remote system
Let Net::OpenSSH take care of everything for you:
my $ssh = Net::OpenSSH->new($ssh_addr);
$ssh->error and die "unable to connect to remote host: " . $ssh->error;
if ($ssh->test('test', '-d', $remote_dir)) {
# do something here!
}
Oh, it seems you are on a Windows machine! You can use Net::SSH::Any there in a similar fashion.

Bash script executing a binary

I have a bash script that I am modifying. The script now also executes a binary. Say something like this
mybin arg1 arg1 The binary takes about 5 minutes to execute and when I execute it from bash directly, it does show the intermediate outputs. When I add it to my script as
`mybin arg1 arg1`
I get the output in the end and bash thinks the output is a command and tries to execute it. So I want to solve 2 things
Show the intermediate output on the screen when I execute the binary from the bash script.
And the output must not be treated to be a command for processing, just regular output
Remove the backticks.
`prog` means "collect the output of prog and interpolate it into the current command", so if `prog` is the only thing on the command line, its output will be executed as another command. This is known as command substitution.
In other words, the two things you don't want to happen are exactly what ` ` is designed to do.

Script takes only first part of double quotes

Yesterday I asked a similar question about escaping double quotes in env variables, although It didn't solve my problem (Probably because I didn't explain good enough) so I would like to specify more.
I'm trying to run a script (Which I know is written in Perl), although I have to use it as a black box because of permissions issue (so I don't know how the script works). Lets call this script script_A.
I'm trying to run a basic command in Shell: script_A -arg "date time".
If I run from the command line, it's works fine, but If I try to use it from a bash script or perl scrip (for example using the system operator), it will take only the first part of the string which was sent as an argument. In other words, it will fail with the following error: '"date' is not valid..
Example to specify a little bit more:
If I run from the command line (works fine):
> script_A -arg "date time"
If I run from (for example) a Perl script (fails):
my $args = $ENV{SOME_ENV}; # Assume that SOME_ENV has '-arg "date time"'
my $cmd = "script_A $args";
system($cmd");
I think that the problem comes from the environment variable, but I can't use the one quote while defining the env variable. For example, I can't use the following method:
setenv SOME_ENV '-arg "date time"'
Because it fails with the following error: '"date' is not valid.".
Also, I tried to use the following method:
setenv SOME_ENV "-arg '"'date time'"'"
Although now the env variable will containe:
echo $SOME_ENV
> -arg 'date time' # should be -arg "date time"
Another note, using \" fails on Shell (tried it).
Any suggestions on how to locate the reason for the error and how to solve it?
The $args, obtained from %ENV as you show, is a string.
The problem is in what happens to that string as it is manipulated before arguments are passed to the program, which needs to receive strings -arg and date time
If the program is executed in a way that bypasses the shell, as your example is, then the whole -arg "date time" is passed to it as its first argument. This is clearly wrong as the program expects -arg and then another string for its value (date time)
If the program were executed via the shell, what happens when there are shell metacharacters in the command line (not in your example), then the shell would break the string into words, except for the quoted part; this is how it works from the command line. That can be enforced with
system('/bin/tcsh', '-c', $cmd);
This is the most straightforward fix but I can't honestly recommend to involve the shell just for arguments parsing. Also, you are then in the game of layered quoting and escaping, what can get rather involved and tricky. For one, if things aren't right the shell may end up breaking the command into words -arg, "date, time"
How you set the environment variable works
> setenv SOME_ENV '-arg "date time"'
> perl -wE'say $ENV{SOME_ENV}' #--> -arg "date time" (so it works)
what I believe has always worked this way in [t]csh.
Then, in a Perl script: parse this string into -arg and date time strings, and have the program is executed in a way that bypasses the shell (if shell isn't used by the command)
my #args = $ENV{SOME_ENV} =~ /(\S+)\s+"([^"]+)"/; #"
my #cmd = ('script_A', #args);
system(#cmd) == 0 or die "Error with system(#cmd): $?";
This assumes that SOME_ENV's first word is always the option's name (-arg) and that all the rest is always the option's value, under quotes. The regex extracts the first word, as consecutive non-space characters, and after spaces everything in quotes.† These are program's arguments.
In the system LIST form the program that is the first element of the list is executed without using a shell, and the remaining elements are passed to it as arguments. Please see system for more on this, and also for basics of how to investigate failure by looking into $? variable.
It is in principle advisable to run external commands without the shell. However, if your command needs the shell then make sure that the string is escaped just right to to preserve quotes.
Note that there are modules that make it easier to use external commands. A few, from simple to complex: IPC::System::Simple, Capture::Tiny, IPC::Run3, and IPC::Run.
I must note that that's an awkward environment variable; any way to ogranize things otherwise?
† To make this work for non-quoted arguments as well (-arg date) make the quote optional
my #args = $ENV{SOME_ENV} =~ /(\S+)\s+"?([^"]+)/;
where I now left out the closing (unnecessary) quote for simplicity

How can I pass a custom string as argv[0] to a program

I have a C program which uses argv[0] inside the program. I understand that argv[0] is the path of the program being executed. I want to pass a custom string as argv[0] to the program instead of its program name. Is there a way to do this in shell?
I read about exec command. But I am unsure about the usage. help exec says I have to pass exec -a <string>
Is there any other way of doing this?
Is there any escape method which I need to use if I am passing special characters or path of another file using exec command?
To clarify the problem:
I am running a program prog1. To enter a particular section in the program I have to give a SIGALRM to the program. This step itself was difficult as I had to create a race around condition to send the signal right when the program starts.
while true;do ./prog1 2; done & while true; do killall -14 prog1; done
The above while loops help me to enter the part of program and that part of program uses argv[0] for a system call. This system call is system(echo something argv[0])
Is there a way to modify the above while loop and put ;/bin/myprogram instead of argv[0].
Bottom line: I need /bin/myprogram to be executed with the privilege of prog1 and it's output.
exec -a is precisely the way to solve this problem.
There are no restrictions that I know of on the string passed as an argument to exec. Normal shell quoting should be sufficient to pass anything you want (as long as it doesn't contain embedded NUL bytes, of course).
The problem with exec is that it replaces the current shell with the named command. If you just want to run a command, you need to spawn a new shell to be replaced; that is as simple as surrounding the command with parentheses:
$ ( exec -a '; /bin/myprogram' bash -c 'echo "$0"'; )
; /bin/myprogram
The brute-force method would be to create your own symlink and run the command that way.
ln -s /path/to/mycommand /tmp/newname
/tmp/newname arg1
rm /tmp/newname
The main problem with this is finding a secure, race-condition-free way to create the symlink that guarantees you run the command you intend to, which is why bash adds a non-standard -a extension to exec so that you don't need such file-system-based workarounds.
Typically, though, commands restrict their behavioral changes to a small, fixed set of possible names. This means that any such links can be created when the program is first installed, and don't need to be created on the fly. In this scenario, there is no need for exec -a, since all possible "virtual" executables already exist.

Ruby system method arguments

I'm quite confused reading the doc of Ruby's system method here. I'm not sure what are commands and what are options. What do I do if I want to execute the following?
wget -pk -nd -P /public/google www.google.com
For security reasons, I'd like to use one of the versions that uses no shell (the second and third forms in the URL I gave, rather than the first)
Consider the examples:
system("echo *")
system("echo", "*")
The first one passes the string 'echo *' to the shell to be parsed and executed; that's why system('echo *') produces the same output as saying echo * from the shell prompt: you get a list of files in the current directory. The corresponding argument form is:
commandline : command line string which is passed to the standard shell
The second one bypasses the shell entirely. It will look for echo in the PATH and then execute it with the string '*' as its argument. Since the shell expands wildcards (at least on unixy systems), the * will stay as a simple * and you'll see * as the output. The corresponding argument form here is:
cmdname, arg1, ... : command name and one or more arguments (no shell)
The third form:
[cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
is used when you want to execute cmdname but have it show up with a different name in ps listings and such. You can see this in action by opening two terminals. Open up irb in one of them and say:
system('sleep', '10')
then quickly switch to the other and look at the ps listing. You should see sleep 10 in there. But, if you give this to irb:
system(['sleep', 'pancakes'], '10')
and check the ps listing, you'll see pancakes 10. Similar two-terminal tricks will show you a shell -c sleep 10 if you say system('sleep 10').
If you supply a Hash as the first argument, then that Hash is used as the environment variables for the spawned process. If you supply a Hash as the final argument, then that Hash is used as options; further documentation on the arguments is, as noted in the system documentation, available under Kernel#spawn.

Resources