I'm feel like I'm missing something pretty obvious here, but I can't seem to figure out what's going on. I have a perl script that I'm calling from C code. The script + arguments is something like this:
my_script "/some/file/path" "arg" "arg with spaces" "arg" "/some/other/file"
When I run it in Windows, Perl correctly identifies it as 5 arguments, whereas when I ran it on the SunOS Unix machine, it identified 8, splitting the arg with spaces into separate args.
Not sure if it makes any difference, but in Windows I'm running it like:
perl my_script <args>
While in Unix I'm just running it as an executable like show above.
Any idea why Unix is not managing that argument properly?
Edit:
Here's the code for calling the perl script:
char cmd[1000];
char *script = "my_script";
char *argument = "\"arg1\" \"arg2\" \"arg3 with spaces\" \"arg4\" \"arg5\"";
sprintf( cmd, "%s %s >1 /dev/null 2>&1", script, arguments);
system( cmd );
That's not exactly it, as I build the argument string a little more dynamically, but that's the gist.
Also, here's my code for reading the arguments in:
($arg1, $arg2, $arg3, $arg4, $arg5) = #ARGV;
I know it's ridiculously naive, but this script will only be run from the C application, so nothing more complex should be required.
Presumably
system("my_script \"/some/file/path\" \"arg\" \"arg with spaces\" \"arg\" \"/some/other/file\");
causes everything to go through bash (because of the need to interpret the shebang line, eating up the quotes you pass). Again, presumably, the problem could be avoided by invoking perl directly rather than relying on the shell to find it (although this might be a problem if the perl on the path is different than the one provided on the shebang line).
Update:
Given your:
char *argument = "\"arg1\" \"arg2\" \"arg3 with spaces\" \"arg4\" \"arg5\"";
you might want to try:
char *argument = "\\\"arg1\\\" \\\"arg2\\\" \\\"arg3 with spaces\\\" \\\"arg4\\\" \\\"arg5\\\"";
Another update:
Thank you for accepting my answer, however, my whole theory might be wrong.
I tried the double-backwhacked version of argument above in GNU bash, version 4.0.28(2)-release (i686-pc-linux-gnu) and it ended up passing
[sinan#kas src]$ ./t
'"arg1"'
'"arg2"'
'"arg3'
'with'
'spaces"'
'"arg4"'
'"arg5"'
whereas the original argument worked like a charm. I am a little puzzled by this. Maybe the shell on the SUN isn't bash or maybe there is something else going on.
Did you forget the quotes on SunOS?
If you do
perl script arg1 arg2 "arg3 with spaces" arg4 arg5
you should be good. Otherwise, try switching shells.
Now that the question was updated, I still can't reproduce your results:
~% cat test.c
#include <stdio.h>
#include <stdlib.h>
int main(void){
char cmd[1000];
char *script = "./my_script";
char *arguments = "\"arg1\" \"arg2\" \"arg3 with spaces\" \"arg4\" \"arg5\"";
sprintf( cmd, "%s %s", script, arguments);
system( cmd );
return 0;
}
~% cat my_script
#!/usr/bin/perl
($arg1, $arg2, $arg3, $arg4, $arg5) = #ARGV;
print "arg1 = $arg1\n";
print "arg2 = $arg2\n";
print "arg3 = $arg3\n";
print "arg4 = $arg4\n";
print "arg5 = $arg5\n";
~% gcc test.c
~% ./a.out
arg1 = arg1
arg2 = arg2
arg3 = arg3 with spaces
arg4 = arg4
arg5 = arg5
~%
There is something else with your configuration.
Previous answer:
Unix shells interpret quoted arguments
as a single argument. You can do a
quick test:
for i in a b "c d e" f; do echo $i; done
The result is what you expect it to
be: "c d e" is treated like a single
argument.
I think you have a problem in your
script, in the argument handling
logic.
Is it possible that the SunOS kernel's handing of shebang interpreters is ludicrously bad? Try running it as "/path/to/perl script args" instead of "script args" and see if anything changes.
Related
I have this script called test.sh:
#!/bin/bash
STR = "Hello World"
echo $STR
when I run sh test.sh I get this:
test.sh: line 2: STR: command not found
What am I doing wrong? I look at extremely basic/beginners bash scripting tutorials online and this is how they say to declare variables... So I'm not sure what I'm doing wrong.
I'm on Ubuntu Server 9.10. And yes, bash is located at /bin/bash.
You cannot have spaces around the = sign.
When you write:
STR = "foo"
bash tries to run a command named STR with 2 arguments (the strings = and foo)
When you write:
STR =foo
bash tries to run a command named STR with 1 argument (the string =foo)
When you write:
STR= foo
bash tries to run the command foo with STR set to the empty string in its environment.
I'm not sure if this helps to clarify or if it is mere obfuscation, but note that:
the first command is exactly equivalent to: STR "=" "foo",
the second is the same as STR "=foo",
and the last is equivalent to STR="" foo.
The relevant section of the sh language spec, section 2.9.1 states:
A "simple command" is a sequence of optional variable assignments and redirections, in any sequence, optionally followed by words and redirections, terminated by a control operator.
In that context, a word is the command that bash is going to run. Any string containing = (in any position other than at the beginning of the string) which is not a redirection and in which the portion of the string before the = is a valid variable name is a variable assignment, while any string that is not a redirection or a variable assignment is a command. In STR = "foo", STR is not a variable assignment.
Drop the spaces around the = sign:
#!/bin/bash
STR="Hello World"
echo $STR
In the interactive mode everything looks fine:
$ str="Hello World"
$ echo $str
Hello World
Obviously(!) as Johannes said, no space around =. In case there is any space around = then in the interactive mode it gives errors as
No command 'str' found
I know this has been answered with a very high-quality answer. But, in short, you cant have spaces.
#!/bin/bash
STR = "Hello World"
echo $STR
Didn't work because of the spaces around the equal sign. If you were to run...
#!/bin/bash
STR="Hello World"
echo $STR
It would work
When you define any variable then you do not have to put in any extra spaces.
E.g.
name = "Stack Overflow"
// it is not valid, you will get an error saying- "Command not found"
So remove spaces:
name="Stack Overflow"
and it will work fine.
I want to do the next bash command, but actually in gdb (so i can debug it):
myProgram "`echo -en '\x41\x41\x41\x41'`"
I'm trying to do this (in gdb):
(dbg) run "`echo -en "\x41\x41\x41\x41"`"
i DON'T mean the stdin redirect:
echo -en "\x41\x41\x41\x41" > command_output.txt
gdb myProgram
(gdb) run < command_output.txt
how to insert Hex values as an agrument to a program in gdb?
you may try
(dbg) run $(echo -en "\x41\x41\x41\x41")
The gdb set args command can set the arguments. Using eval, which runs its arguments through printf, we can insert almost arbitrary characters using hex escapes. (We're limited by the fact that gdb will typically invoke the target using a shell, so it helps to add single quotes around each argument).
(gdb) eval "set args '%s'", "\x41\x41\x20\x41"
You can't do that without a process to debug.
That's another gdb limitation. An alternative that doesn't make gdb want to allocate memory in the target is to give eval only %c conversions and integer arguments, like this:
(gdb) eval "set args '%c%c%c%c'", 0x41, 0x41, 0x20, 0x41
But having to put the exact number of %c conversions into that format string is tedious, so let's stick with the single %s. We need to start the process, even though we're going to restart it right after we set the args using eval.
(gdb) start
Starting program: /home/mp/argprint
Temporary breakpoint 1, main (argc=1, argv=0x7ffffffee2b8) at argprint.c:4
4 for(int i=0; i < argc; i++) {
(gdb) eval "set args '%s'", "\x41\x41\x20\x41"
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/mp/argprint 'AA A'
arg 0 is <</home/mp/argprint>>
arg 1 is <<AA A>>
If you have an old gdb that doesn't have the eval command but does have Python, you can do this:
(gdb) python gdb.execute("set args '\x41\x41\x20\x41'")
I want to execute an external program in lua. Usually this can be done with
os.execute("run '"..arg0.."' 'arg1' arg2")
The problem with this approach is if I want to pass user input as string to an external program, user input could be '; evil 'h4ck teh system' ' and the script from above would execute like this:
/bin/bash -c "run ''; evil 'h4ck teh system' '' 'arg1' arg2"
Another problem occurs when I have '$var' as argument and the shell replaces this with its environment variable. In my particular case I have something like [[program 'set title "$My Title$"']] – so nested strings – and program parses "$My Title$" (with escape sequences) differently than '$My Title$' (as it is). Because I want to set the title as it, the best way is to have arguments like this: 'My Title'. But now the command have to be:
os.execute([[run "set title '$My Title$'"]])
But now – as I said – $My will be replaced with an empty string, because the environment does not know any variable named $My and because, I never wanted it to be replaced.
So I am looking for the usual approach with
execv("run", {"set title '"..arg0.."'", arg1, arg2})
local safe_unquoted = "^[-~_/.%w%%+,:#^]*$"
local function q(text, expand) -- quoting under *nix shells
-- "expand"
-- false/nil: $var and `cmd` must NOT be expanded (use single quotes)
-- true: $var and `cmd` must be expanded (use double quotes)
if text == "" then
text = '""'
elseif not text:match(safe_unquoted) then
if expand then
text = '"'..text:gsub('["\\]', '\\%0')..'"'
else
local new_text = {}
for s in (text.."'"):gmatch"(.-)'" do
new_text[#new_text + 1] = s:match(safe_unquoted) or "'"..s.."'"
end
text = table.concat(new_text, "\\'")
end
end
return text
end
function execute_commands(...)
local all_commands = {}
for k, command in ipairs{...} do
for j = 1, #command do
if not command[j]:match"^[-~_%w/%.]+$" then
command[j] = q(command[j], command.expand)
end
end
all_commands[k] = table.concat(command, " ") -- space is arguments delimiter
end
all_commands = table.concat(all_commands, ";") -- semicolon is commands delimiter
return os.execute("/bin/bash -c "..q(all_commands))
end
Usage examples:
-- Usage example #1:
execute_commands(
{"your/program", "arg 1", "$arg2", "arg-3", "~/arg4.txt"},
{expand=true, "echo", "Your program finished with exit code $?"},
{"ls", "-l"}
)
-- The following command will be executed:
-- /bin/bash -c 'your/program '\''arg 1'\'' '\''$arg2'\'' arg-3 ~/arg4.txt;echo "Your program finished with exit code $?";ls -l'
$arg2 will NOT be expanded into value because of single quotes around it, as you required.
Unfortunately, "Your program finished with exit code $?" will NOT be expanded too (unless you explicitly set expand=true).
-- Usage example #2:
execute_commands{"run", "set title '$My Title$'", "arg1", "arg2"}
-- the generated command is not trivial, but it does exactly what you need :-)
-- /bin/bash -c 'run '\''set title '\''\'\'\''$My Title$'\''\'\'' arg1 arg2'
In some of the code I was going through I found that if was using braces {} for someplace and parenthesis (()) for some other. Can someone tell me the exact meaning and where to use which one?
if [ "$1" = "--help" ]
if (( $# != 3 ))
The bracket [ is a built-in command of the shell; you can also call it as test:
if [ a = b ]
then ...
equals:
if test a = b
then ...
The syntax of the test command is rather text-oriented (see the bash man page for details at chapter CONDITIONAL EXPRESSIONS).
The braces {…} are shell syntax and used for grouping commands (without creating a subshell):
{ date; ls; echo $$; } > 1>&2
This will execute date, ls, and echo $$ and redirect all their output to stderr.
The parenthesis (…) are shell-syntax and used for creating a subshell:
(date; ls; echo $$) > 1>&2
Like above but the PID ($$) given out is that of the subshell.
The difference between grouping and subshell is delicate (and out of scope here).
The doubled brackets [[…]] are shell syntax but otherwise behave like the single bracket [ command. The only difference is for using < etc. for string comparison and locale support.
The doubled parentheses ((…)) are equivalent to using the let builtin shell command. They basically allow number-oriented expressions to be evaluated (ARITHMETIC EVALUATION). < and > sort numerically (instead of lexicographically) etc. Also, in some constructs like for ((i=0; i<10; i++)); do echo "$i"; done they are used as a fixed syntax.
Dollar-parenthesis $(…) result in the output of the command they enclose:
echo "$(date)" # a complicated way to execute date
Dollar-brackets $[…] are deprecated and should be replaced by dollar-double-parentheses.
Dollar-double-parentheses $((…)) result in the value of the numerical expression they enclose:
echo "$((4 + 3 * 2))" # should print 10
Dollar-braces ${…} result in the variable expansion they enclose. In the simplest case this is just a variable name, then they evaluate to the variable value:
a=foo
echo "${a}" # prints foo
This can (and often is) abbreviated by stripping the braces: $a
But it also can be more complex like ${a:-"today is $(date)"}. See Parameter Expansion in the bash man page for details.
Redirection-parenthesis <(…) and >(…) create a subprocess, a file descriptor its output/input is associated with, and a pseudo file associated with that descriptor. It can be used to pass the output of a program as a seeming file to another program: diff <(sleep 1; date) <(sleep 2; date)
I am new to shell scripts, I am trying to create a simple function which will return the concatenated two strings that are passed as parameters. I tried with below code
function getConcatenatedString() {
echo "String1 $1"
echo "String2 $2"
str=$1/$2
echo "Concatenated String ${str}"
echo "${str}"
}
//I am calling the above function
constr=$(getConcatenatedString "hello" "world")
echo "printing result"
echo "${constr}"
echo "exit"
I see the below output when running the script with above code,
printing result
String1 hello
String2 world
Concatenated String hello/world
hello/world
exit
If you look at the code I am first calling the function and then I am echoing "printing result" statement, but the result is first comes the "printing result" and echos the statement inside the function. Is the below statement calling the function
constr=$(getConcatenatedString "hello" "world")
or
echo ${constr}
is calling the function ?
Because if I comment out #echo ${constr} then nothing is getting echoed !!! Please clarify me.
The first is calling the function and storing all of the output (four echo statements) into $constr.
Then, after return, you echo the preamble printing result, $constr (consisting of four lines) and the exit message.
That's how $() works, it captures the entire standard output from the enclosed command.
It sounds like you want to see some of the echo statements on the console rather than capturing them with the $(). I think you should just be able to send them to standard error for that:
echo "String1 $1" >&2
paxdiablo's solution is correct. You cannot return a string from a function, but you can capture the output of the function or return an integer value that can be retrieved by the caller from $?. However, since all shell variables are global, you can simply do:
getConcatenatedString() { str="$1/$2"; }
getConcatenatedString hello world
echo "Concatenated String ${str}"
Note that the function keyword is redundant with (), but function is less portable.
A more flexible, but slightly harder to understand approach is to pass a variable name, and use eval so that the variable becomes set in the caller's context (either a global or a function local). In bash:
function mylist()
{
local _varname=$1 _p _t
shift
for _p in "$#"; do
_t=$_t[$_p]
done
eval "$_varname=\$_t"
}
mylist tmpvar a b c
echo "result: $tmpvar"
On my Linux desktop (bash-3.2) it's approx 3-5x faster (10,000 iterations) than using ``, since the latter has process creation overheads.
If you have bash-4.2, its declare -g allows a function to set a global variable, so you can replace the unpretty eval with:
declare -g $_varname="$_t"
The eval method is similar to TCL's upvar 1, and declare -g is similar to upvar #0.
Some shell builtins support something similar, like bash's printf with "-v", again saving process creation by assigning directly to a variable instead of capturing output (~20-25x faster for me).