Using a parameter with exec in Ruby - ruby

I'm trying to execute the command exec when I give it a parameter by console, but I don´t know how to make it.
exec('ls -l #{argv[1]}')
Argv[1] is the parameter I pass by console but it doesn´t do anything.

Unless you need your command to be executed by a shell (such as, you redirect to/from a file), you can pass a list of arguments to exec:
exec 'ls', '-l', ARGV[1]
You're aware that exec replaces the running ruby process? Do you want system instead?
https://ruby-doc.org/core-2.5.0/Process.html#method-c-exec
https://ruby-doc.org/core-2.5.0/Kernel.html#method-i-system

There are several small issues in your code:
variables are not interpolated in single-quote strings; this means your script always tries to execute ls -l #{argv[1]} (as it is written here).
there is no variable, constant or method of class Object that is named argv; there is a global constant named ARGV that contains the command line arguments.
ARGV does not contain the script name (it is stored in a separate property) but only its positional parameters; consequently, the first argument in the command line is stored at index 0, not 1.
Putting together all of the above, your script should be:
exec("ls -l #{ARGV[0]}")

Related

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.

How do I have a variable in a bash script correspond to the url in wget

For example, I have a bash script named getimage. In the script I have:
wget http://www.example.com/images/1.jpg
If I type bash getimage in the terminal, it would download the picture.
But what I want is to type bash getimage 2 or bash getimage 3 and so on in the terminal and download 2.jpg or 3.jpg respectively.
Basically, I want to type a number in terminal (bash getimage number) and have it correspond to wget http://www.example.com/images/number.jpg.
As Aaron mentioned in comments, you need to give the variables externally by specifying them as $1 or $2 etc in your script.
The way to do this is-
wget http://www.example.com/images/"$1".jpg
Then launch your script as bash getimage 1
If (as I suspect) you want to wget in a loop, do-
for i in {1..10} ; do bash getimage "$i" ; done
Or from the commandline,
for i in {1..10} ; do wget http://www.example.com/images/"$i".jpg ; done
Here's what I would do :
#!/bin/bash
image_name=${1-1}
wget "http://www.example.com/images/$image_name.jpg"
The script is to be called as getimage x. If x is omitted, 1 will be chosen as a default value.
The first line is a shebang : it tells your shell to execute the script with the specified executable, /bin/bash.
The second line defines an image_name variable and assigns it the result of an expression. That expression is a parameter expansion, which will return $1 (that is the first parameter passed to the script) if it is defined, or 1 either. It's hard to see in this case, but the syntax is ${variable-default} where variable is a variable name whose value you want, and default a default used when that variable isn't defined.
The third line is your command, where the static 1 has been replaced by a reference to the image_name variable. Note that "" have been added in case the image_name contains spaces : that would break the url in two parts that would be understood by wget as two separate parameters. Don't use '' though, the $variable contained in the string wouldn't be expanded to their value.
A minimal script for what you want would be the following.
#!/usr/bin/env bash
wget "http://www.example.com/images/${1}.jpg"
The first line is called a shebang The shebang tells the script which program to run it in. In this case we use the env program to find the default version of bash in the current environment.
On the second line we simply run your wget command, but we pass in the variable $1. This is a special variable called a positional parameter, in this case the first argument passed to the script.
To run the command, make it executable with chmod +x getimage and then call it like so:
./getimage 1
./getimage 2
etc.

Carefully mimicking Argv[0] with bash

I'm trying to write a bash wrapper script that very carefully mimics the value of argv[0]/$0. I'm using exec -a to execute a separate program with the wrapper's argv[0] value. I'm finding that sometimes bash's $0 doesn't give the same value I'd get in a C-program's argv[0]. Here's a simple test program that demonstrates the difference in both C and bash:
int main(int argc, char* argv[0])
{
printf("Argv[0]=%s\n", argv[0]);
return 0;
}
and
#!/bin/bash
echo \$0=$0
When running these programs with the full (absolute or relative) path to the binary, they behave the same:
$ /path/to/printargv
Argv[0]=/path/to/printargv
$ /path/to/printargv.sh
$0=/path/to/printargv.sh
$ to/printargv
Argv[0]=to/printargv
$ to/printargv.sh
$0=to/printargv.sh
But when invoking them as if they are in the path, I get different results:
$ printargv
Arv[0]=printargv
$ printargv.sh
$0=/path/to/printargv.sh
Two questions:
1) Is this intended behavior that can be explained, or is this a bug?
2) What's the "right" way to achieve the goal of carefully mimicking argv[0]?
edit: typos.
What you're seeing here is the documented behaviour of bash and execve (at least, it is documented on Linux and FreeBSD; I presume that other systems have similar documentation), and reflects the different ways that argv[0] is constructed.
Bash (like any other shell) constructs argv from the provided command line, after having performed the various expansions, resplit words as necessary, and so on. The end result is that when you type
printargv
argv is constructed as { "printargv", NULL } and when you type
to/printargv
argv is constructed as { "to/printargv", NULL }. So no surprises there.
(In both cases, had there been command line arguments, they would have appeared in argv starting at position 1.)
But the execution path diverges at that point. When the first word in the command line includes a /, then it is considered to be a filename, either relative or absolute. The shell does no further processing; it simply calls execve with the provided filename as its filename argument and the argv array constructed previously as its argv argument. In this case, argv[0] precisely corresponds to the filename
But when the command has no slashes:
printargv
the shell does a lot more work:
First, it checks to see if the name is a user-defined shell function. If so, it executes it, with $1...$n taken from the argv array already constructed. ($0 continues to be argv[0] from the script invocation, though.)
Then, it checks to see if the name is a built-in bash command. If so, it executes it. How built-ins interact with command-line arguments is out of scope for this answer, and is not really user-visible.
Finally, it attempts to find the external utility corresponding with the command, by searching through the components of $PATH and looking for an executable file. If it finds one, it calls execve, giving it the path that it found as the filename argument, but still using the argv array consisting of the words from the command. So in this case, filename and argv[0] do not correspond.
So, in both cases, the shell ends up calling execve, providing a filepath (possibly relative) as the filename argument and the word-split command as the argv argument.
If the indicated file is an executable image, there is nothing more to say, really. The image is loaded into memory, and its main is called with the provided argv vector. argv[0] will be a single word or a relative or absolute path, depending only on what was originally typed.
But if the indicated file is a script, the loader will produce an error and execve will check to see if the file starts with a shebang (#!). (Since Posix 2008, execve will also attempt to run the file as a script using the system shell, as though it had #!/bin/sh as a shebang line.)
Here's the documentation for execve on Linux:
An interpreter script is a text file that has execute permission enabled and whose first line is of the form:
#! interpreter [optional-arg]
The interpreter must be a valid pathname for an executable file. If the filename argument of execve() specifies an interpreter script, then interpreter will be invoked with the following arguments:
interpreter [optional-arg] filename arg...
where arg... is the series of words pointed to by the argv argument of execve(), starting at argv[1].
Note that in the above, the filename argument is the filename argument to execve. Given the shebang line #!/bin/bash we now have either
/bin/bash to/printargv # If the original invocation was to/printargv
or
/bin/bash /path/to/printargv # If the original invocation was printargv
Note that argv[0] has effectively disappeared.
bash then runs the script in the file. Prior to executing the script, it sets $0 to the filename argument it was given, in our example either to/printargv or /path/to/printargv, and sets $1...$n to the remaining arguments, which were copied from the command-line arguments in the original command line.
In summary, if you invoke the command using a filename with no slashes:
If the filename contains an executable image, it will see argv[0] as the command name as typed.
If the filename contains a bash script with a shebang line, the script will see $0 as the actual path to the script file.
If you invoke the command using a filename with slashes, in both cases it will see argv[0] as the filename as typed (which might be relative, but will obviously always have a slash).
On the other hand, if you invoke a script by invoking the shell interpreter explicitly (bash printargv), the script will see $0 as the filename as typed, which not only might be relative but also might not have a slash.
All that means that you can only "carefully mimic argv[0]" if you know what form of invoking the script you wish to mimic. (It also means that the script should never rely on the value of argv[0], but that's a different topic.)
If you are doing this for unit testing, you should provide an option to specify what value to provide as argv[0]. Many shell scripts which attempt to analyze $0 assume that it is a filepath. They shouldn't do that, since it might not be, but there it is. If you want to smoke those utilities out, you'll want to supply some garbage value as $0. Otherwise, your best bet as a default is to provide a path to the scriptfile.

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.

Ruby. The strange argv[0] element in the array being an argument of the spawn method

As is known, the Ruby's Kernel#spawn method executes the specified command and returns its pid. The method can accept either a whole command line as a single argument, a command name and any number of the command's arguments or an array where the first element is the command itself and the second is, according to the documentation, the strange variable argv[0]. As it turned out, the variable has nothing to do with the Ruby's ARGV[0].
What is this variable? What does it contain?
Thanks.
Debian GNU/Linux 6.0.2;
Ruby 1.9.3-p0.
I don't think it's a variable at all.
When executing a command (in the general case), the arguments go into argv[1] to argv[*n*]. The name of the command executed can be found in argv[0]. (For Ruby applications, they will be placed in ARGV, for C applications they can be accessed using the argc and argv arguments to main.)
By default, argv[0] will be the same as the command started. However, if you use following form:
exec(["alpha", "beta"])
The program alpha will be executed, but it's argv[0] will be beta.

Resources