How can I preserve command line spaces in a linux application? - linux

As per this question here, I'm deploying a linux application on some local servers using a shell script that looks like:
#!/bin/sh
export LD_LIBRARY_PATH=./libs:$LD_LIBRARY_PATH
exec ./TheBinary $*
When I run TheBinary without these wrappers (but after having modified LD_LIBRARY_PATH, which I want to do via the script post-deployment), I can preserve spaces in the command line arguments using double quotes ("). But the above script appears to sanitize them away; how can I modify this script to respect spaces in command line arguments that are wrapped in double quotes?

#!/bin/bash
export LD_LIBRARY_PATH=./libs:$LD_LIBRARY_PATH
exec ./TheBinary "$#"
I have no real idea whether /bin/sh supports that syntax

Related

Why do quotes in shell scripts behave differently from quotes in shell commands?

I'm using WSL (Ubuntu 18.04) on Windows 10 and bash.
I have a file filename.gpg with the content:
export SOME_ENV_VAR='123'
Now I run the following commands:
$ $(gpg -d filename.gpg)
$ echo $SOME_ENV_VAR
'123' <-- with quotes
However, if I run it directly in the shell:
$ export SOME_ENV_VAR='123'
$ echo $SOME_ENV_VAR
123 < -- without quotes
Why does it behave like this? Why is there a difference between running a command using $() and running it directly?
Aside: I got it working using eval $(gpg -d filename), I have no idea why this works.
Quotes in shell scripts do not behave differently from quotes in shell commands.
With the $(gpg -d filename.gpg) syntax, you are not executing a shell script, but a regular single command.
What your command does
It executes gpg -d filename.gpg
From the result, it takes the first (IFS-separated) word as the command to execute
It takes every other (IFS-separated) words, including words from additional lines, as its parameters
It executs the command
From the following practical examples, you can see how it differs from executing a shell script:
Remove the word export from filename.gpg: the command is then SOME_ENV_VAR='123' which is not understood as a variable assignment (you will get SOME_ENV_VAR='123': command not found).
If you add several lines, they won't be understood as separated command lines, but as parameters to the very first command (export).
If you change export SOME_ENV_VAR='123' to export SOME_ENV_VAR=$PWD, SOME_ENV_VAR will not contain the content of variable PWD, but the string $var
Why is it so?
See how bash performs expansion when analyzing a command.
There are many steps. $(...) is called "command substitution" and is the fourth step. When it is done, none of the previous steps will be performed again. This explains why your command does not work when you remove the export word, and why variables are not substituted in the result.
Moreover "quote Removal" is the last step and the manual reads:
all unquoted occurrences of the characters ‘\’, ‘'’, and ‘"’ that did
not result from one of the above expansions are removed
Since the single quotes resulted from the "command substitution" expansion, they were not removed. That's why the content of SOME_ENV_VAR is '123' and not 123.
Why does eval work?
Because eval triggers another complete parsing of its parameters. The whole set of expansions is run again.
From the manual:
The arguments are concatenated together into a single command, which is then read and executed
Note that this means that you are still running one single command, and not a shell script. If your filename.gpg script has several lines, subsequent lines will be added to the argument list of the first (and only) command.
What should I do then?
Just use source along with process substitution.
source <(gpg -d filename.gpg)
Contrary to eval, source is used to execute a shell script in the current context. Process substitution provides a pseudo-filename that contains the result of the substitution (i.e. the output of gpg).

How can I escape arguments passed in bash script command line

I have one variable, which is coming from some where like:
VAR1='hhgfhfghhgf"";2Ddgfsaj!!!$#^$\'&%*%~*)_)(_{}||\\/'
Now i have command like this
./myscript.sh '$VAR1'
I am getting that $VAR1 from some diff process and when I display it look exactly as its above.
Now that command is failing as there is already single quote inside variable. In the process where I use it it is expanded at that point, which causes that error.
I have control over myscript.sh but not above command.
Is there any way I can get variable inside my script?
What you are saying is not possible to failing when passing to your script. Might your script has processing issue (or a command where this argument will passing into it) which cannot expand the variable correctly. You can either use printf with %q modifier to escape all special characters then pass it to your script:
./myscript.sh "$(printf '%q\n' "$VAR1")"
... or do the same within your script before you wanted to pass to some other commands:
VAR2="$(printf '%q\n' "$VAR1")"

bash + Linux + how to ignore the character "!"

I want to send little script to remote machine by ssh
the script is
#!/bin/bash
sleep 1
reboot
but I get event not found - because the "!"
ssh 183.34.4.9 "echo -e '#!/bin/bash\nsleep 1\reboot>'/tmp/file"
-bash: !/bin/bash\nsleep: event not found
how to ignore the "!" char so script will so send successfully by ssh?
remark I cant use "\" before the "!" because I get
more /tmp/file
#\!/bin/bash
sleep 1
Use set +H before your command to disable ! style history substitution:
set +H
ssh 183.34.4.9 "echo -e '#!/bin/bash\nsleep 1\reboot>'/tmp/file"
# enable hostory expnsion again
set -H
I think your command line is not well formated. You can send this:
ssh 183.34.4.9 'echo -e "#!/bin/bash\nsleep 1\nreboot">/tmp/file'
When I say "not well formated" I mean you put ">" inside the "echo" and you forgot to add "n" before "reboot", and you put "\reboot", wich will be interpreted as "CR" (carriage return) followed by "eboot" command (which I don't think that exists).
But what did the trick here is to invert the comas changing (') with (") and viceversa.
Bash is running interactively (which means that you are feeding commands to it from the standard input and not exec(2)ing a command from a shell script) so you don't need to include the line #!/bin/bash in that case (even more, bash should just ignore it, but not the included bang, as it is part of the active history mechanism)
But why? the first two characters in an executable file (any file capable of being exec(2)ed from secondary storage, not your case) have a special meaning (for the kernel and for the shell): they are the magic number that identifies the kind of executable file the kernel is loading. This allows the kernel to select the proper executable loading routines depending on the binary executable format (and what allows you for example to execute BSD programs in linux kernels, and viceversa)
A special value for this magic numbers is composed by the two characters # and ! (in that order) that forces the kernel to read the complete first line of that file and load the executable file specified in that line instead, allowing you to execute shell scripts for different interpreters directly from the command line. And it is done on purpose, as the # character is commonly in shell script parlance a comment character. This only happens when the shell that is interpreting the commands is not an interactive shell. When the shell loads a script with those characters, it normally reads the first line also to check if it has the #! mark and load the proper interpreter, by replicating the kernel function that does this. Despite of being a comment for the shell, it does this to allow to treat as executables files that are not stored on secondary storage (the only ones the exec(2) system call can deal with), but coming from stdin (as happens to yours).
As your shell is running interactively and you do want to execute its commands without a shell change, you don't need that line and can completely eliminate it without having to disable the bang character.
Sorry, but the solution given about executing the shell with -H option will probably not be viable, as the shell executing the commands is the login shell in the target machine, so you cannot provide specific parameters to it (parameters are selected by the login(8) program and normally don't include arbitrary parameters like -H).
The best solution is to fully eliminate the #!/bin/bash line, as you are not going to exec(2) that program in the target. In case you want to select the shell from the input line (case the user has a different shell installed as login shell), it is better to invoke the wanted shell in the command line and pass it (through stdin, or making it read the shell script as a file) the shell commands you wan to execute (but again, without the #! line).
NOTE
Its important to ensure you'll execute the whole thing, so it's best to pass all the script contents in the destination target, and once assured you have passed the whole thing to execute it as a whole. Then your #! first line will be properly processed, as the executable will be run by means of an exec(2) made from the kernel.
Example:
DIRECTORY=/bla/bla
FILE=/path/to/file
OUTPUT=/path/to/output
# this is the command we want to pass through the line
cat <<EOF | ssh user#target "cat >>/tmp/shell.sh"
cd $DIRECTORY
foo $FILE >$OUTPUT
exit 0
EOF
# we have copied the script file in a remote /tmp/shell.sh
# and we are sure it has passed correctly, so it's ready
# for local execution there.
# now, execute it.
# the remote shell won't be interactive, and you'll ensure that it is /bin/bash
ssh user#target "/bin/bash /tmp/shell.sh" >remote_shell.out
A more sophisticate system is one that allows to to sign the shell script before sending, and verify the script signature before executing it, so you are protected against possible trojan horse attacks. But this is out of scope on this explanation.
Another alternative is to use the batch(2) command remotely and pass it all the commands you want executed. you'll get a sessionless executing environment, more suitable to the task you are demanding (despite the fact that you'll get the script output by email to the target user running the script)
Interactively, beware that ! triggers history expansion inside double quotes
from here: https://riptutorial.com/bash/example/2465/quoting-literal-text
my recommended solution is to use single quotes to define the string (and either escape single quotes \' or use double quotes " within the string):
ssh 183.34.4.9 'echo -e "#!/bin/bash\nsleep 1\reboot>"/tmp/file'

Escape newline character in heredoc on solaris

I am using bash and this works on Linux:
read -r -d '' VAR<<-EOF
Hello\nWorld
EOF
echo $VAR > trail
i.e the contents of the file on Linux is
Hello\nWorld
When i run on Solaris
trial file has
Hello
World
The newline(\n) is being replaced with a newline. How can i avoid it?
Is it a problem with heredoc or the echo command?
[UPDATE]
Based on the explanation provided here:
echo -E $VAR > trail
worked fine on Solaris.
The problem is with echo. Behavior is defined in POSIX, where interpretting \n is part of XSI but not basic POSIX itself.
You can avoid this on all platforms using printf (which is good practice anyways):
printf "%s\n" "$VAR"
This is not a problem for bash by the way. If you had used #!/usr/bin/env bash as the shebang (and also not run the script with sh script), behavior would have been consistent.
If you use #!/bin/sh, you'll get whichever shell the system uses as a default, with varying behaviors like this.
To complement #that other guy's helpful answer:
Even when it is bash executing your script, there are several ways in which the observed behavior - echo by default interpreting escape sequences such as \n - can come about:
shopt -s xpg_echo could be in effect, which makes the echo builtin interpret \ escape sequences by default.
enable -n echo could be in effect, which disables the echo builtin and runs the external executable by default - and that executable's behavior is platform-dependent.
These options are normally NOT inherited when you run a script, but there are still ways in which they could take effect:
If your interactive initialization files (e.g., ~/.bashrc) contain commands such as the above and you source (.) your script from an interactive shell.
When not sourcing your script: If your environment contains a BASH_ENV variable that points to a script, that script is sourced before your script runs; thus, if that script contains commands such as the above, they will affect your script.

Command works from command line but not from shell script

I am not much familiar with shell script. I want to modify a script but it's giving me error "No such file or directory" after I change it.
But that command works over the command prompt.
Here is the line which causes problem. (Even not sure how below command spawns a process.)
_T_COMMAND_=1 "valgrind ---tool=memcheck --trace-children=yes command"
same thing works if I run command as
$valgrind ---tool=memcheck --trace-children=yes command
Any idea?
Don't put the command in double quotes.
_T_COMMAND_=1 valgrind ---tool=memcheck --trace-children=yes command
The general syntax is simply
[var=value ...] cmd [args]
which will set the environment variable var to value for the duration of cmd. You can set several variables in this way.
Alternatively, set the variable and export it; then it will remain set for the remainder of the current shell's lifetime, and be exposed to subprocesses (that's what the export does).
_T_COMMAND_=1
export _T_COMMAND_
valgrind ---tool=memcheck --trace-children=yes command
Similarly, valgrind processes its options, then runs the specified command (with any options) as a subprocess.
A single command in double quotes is harmless, because the shell will strip the quotes before the kernel sees the argument. A string with spaces in double quotes will be preserved as a single argument, while without quotes, it becomes multiple arguments. Behold:
bash$ perl -le 'print "<<$_>>" for #ARGV' "foo bar" baz quux
<<foo bar>>
<<baz>>
<<quux>>
or just as well, add harmless but no doubt rather confusing double quotes around everything which isn't already quoted:
bash$ "perl" "-le" 'print "<<$_>>" for #ARGV' "yowza"
<<yowza>>
The shell parses this into
<<perl>>
<<-le>>
<<print "<<$_>>" for #ARGV>>
<<yowza>>
and removes the (outer) quotes in the process.

Resources