Input from within shell script - shell

I have a script that calls an application that requires user input, e.g. run app that requires user to type in 'Y' or 'N'.
How can I get the shell script not to ask the user for the input but rather use the value from a predefined variable in the script?
In my case there will be two questions that require input.

You can pipe in whatever text you'd like on stdin and it will be just the same as having the user type it themselves. For example to simulating typing "Y" just use:
echo "Y" | myapp
or using a shell variable:
echo $ANSWER | myapp
There is also a unix command called "yes" that outputs a continuous stream of "y" for apps that ask lots of questions that you just want to answer in the affirmative.

If the app reads from stdin (as opposed to from /dev/tty, as e.g. the passwd program does), then multiline input is the perfect candidate for a here-document.
#!/bin/sh
the_app [app options here] <<EOF
Yes
No
Maybe
Do it with $SHELL
Quit
EOF
As you can see, here-documents even allow parameter substitution. If you don't want this, use <<'EOF'.

the expect command for more complicated situations, you system should have it. Haven't used it much myself, but I suspect its what you're looking for.
$ man expect
http://oreilly.com/catalog/expect/chapter/ch03.html

I prefer this way: If You want multiple inputs... you put in multiple echo statements as so:
{ echo Y; Y; } | sh install.sh >> install.out
In the example above... I am feeding two inputs into the install.sh script. Then... at the end, I am piping the script output to a log file to be archived and viewed for later.

Related

Use a variable on a script command line when its value isn't set until after the script starts

How to correctly pass to the script and substitute a variable that is already defined there?
My script test.sh:
#!/bin/bash
TARGETARCH=amd64
echo $1
When I enter:
bash test.sh https://example/$TARGETARCH
I want to see
https://example/amd64
but I actually see
https://example/
What am I doing wrong?
The first problem with the original approach is that the $TARGETARCH is removed by your calling shell before your script is ever invoked. To prevent that, you need to use quotes:
./yourscript 'https://example.com/$TARGETARCH'
The second problem is that parameter expansions only happen in code, not in data. This is, from a security perspective, a Very Good Thing -- if data were silently treated as code it would be impossible to write secure scripts handling untrusted data -- but it does mean you need to do some more work. The easy thing, in this case, is to export your variable and use GNU envsubst, as long as your operating system provides it:
#!/bin/bash
export TARGETARCH=amd64
substitutedValue=$(envsubst <<<"$1")
echo "Original value was: $1"
echo "Substituted value is: $substitutedValue"
See the above running in an online sandbox at https://replit.com/#CharlesDuffy2/EcstaticAfraidComputeranimation#replit.nix
Note the use of yourscript instead of test.sh here -- using .sh file extensions, especially for bash scripts as opposed to sh scripts, is an antipattern; the essay at https://www.talisman.org/~erlkonig/documents/commandname-extensions-considered-harmful/ has been linked by the #bash IRC channel on this topic for over a decade.
For similar reasons, changing bash yourscript to ./yourscript lets the #!/usr/bin/env bash line select an interpreter, so you aren't repeating the "bash" name in multiple places, leading to the risk of those places getting out of sync with each other.

Can I set variables based on command arguments in a Shell script? [duplicate]

This question already has answers here:
What is the best way to parse command line options in bash shell?
(1 answer)
Specify command line arguments like name=value pairs for shell script
(5 answers)
Closed 1 year ago.
My goal is to make a shell script that I can run that will generate a static HTML page and I would like to change some of the data in the file, like some text or a class name and I would like to get those variables from a command line argument, like this:
sh createfile.sh --title "Example" --author "Example"
Can you even handle command arguments with a shell script?
There are a lot of ways to parse command line arguments in a script. The most basic is just to reference them via $1, $2, etc. But for your use case, it's probably easier to not bother. Just do something like:
$ cat createfile.sh
#!/bin/sh
cat << EOF
${title?}
Written by ${author?}
EOF
$ author=doug title='my document' sh createfile.sh
my document
Written by doug
There certainly are reasons to avoid using the environment this way, but if you want to pass the value as parameters there's no reason to enforce using -- as an indicator. Just do:
#!/bin/sh
for x; do
if ! test "$x" = "${x#*=}"; then
eval "$x"
fi
done
cat << EOF
${title?}
Written by ${author?}
EOF
$ sh createfile.sh author=foo title=bar
bar
Written by foo
By avoiding the environment, you prevent accidentally grabbing unexpected values. Also, this will work if you are using one of the shells from the csh family. This is certainly not robust, and any use of eval is suspect, but these are 2 reasonably useful techniques.

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.

Display exact output of Unix command from a Bash script

I am trying to print out the output displayed from a command passed into a bash script. The problem I am trying to solve is how to get the output to look exactly like it would if you ran the command from the shell. For example, when I run ls, I see different colors for directories vs. files.
Here is some sample code of what I have so far:
#!/bin/bash
command="$#"
output=`$command`
echo "$output"
So my shell script takes in a command, runs the command, then prints the output. I know that I can customize the color of the output using color codes and echo -e, but I want the output to look just as it does when I run the command from the shell. Any idea of how I can do this?
If all you need is to display the output, you can run the command inline within your script (just let it write to stdout directly, without storing its output in some variable).
That is, you can replace:
output=`$command`
echo $output
with:
$command
or
eval $command
If you also need that output for some kind of processing, that would be a bit tricky. You can (for instance) use | tee /var/tmp/some-temp-file.$$ and then read the output from the temporary file.
Some programs, such as ls, check whether standard output isatty() and behave differently depending on that. If you are capturing the command's output in a variable, the shell redirects its standard output to a pipe which is not a TTY.
There is not much you can do about this except reading the manual page for each individual command to find out whether it supports special options that make its behavior independent of whether its standard output is piped. For ls in particular, you can use the dir program that will always produce human-friendly column formatted output as an alternative.
On a more general level: What you are trying to do seems to be a rather strange thing anyway. I'm sure there is a more robust solution to do what you are trying to accomplish.
Why not just have your script as:
#!/bin/bash
"$#"
It will run any command line passed as argument and print the output unmodified.

Bash- passing input without shell interpreting parameter expansion chars

So I have a script where I type the script.sh followed by input for a set of if-else statements. Like this:
script.sh fnSw38h$?2
The output echoes out the input in the end.
But I noticed that $? is interpreted as 0/1 so the output would echo:
fnSw38h12
How can I stop the shell from expanding the characters and take it face value?
I looked at something like opt noglob or something similar but they didn't work.
When I put it like this:
script.sh 'fnSw38h$?2'
it works. But how do I capture that within single quotes ('') when I can't state variables inside it like Var='$1'
Please help!
How to pass a password to a script
I gather from the comments that the true purpose of this script is to validate a password. If this is an important or sensitive application, you really should be using professional security tools. If this application is not sensitive or this is just a learning exercise, then read on for a first introduction to the issues.
First, do not do this:
script.sh fnSw38h$?2
This password will appear in ps and be visible to any user on the system in plain text.
Instead, have the user type the password as input to the script, such as:
#!/bin/sh
IFS= read -r var
Here, read will gather input from the keyboard free from shell interference and it will not appear in ps output.
var will have the password for you to verify but you really shouldn't have plain text passwords saved anywhere for you to verify against. It is much better to put the password through a one-way hash and then compare the hash with something that you have saved in a file. For example:
var=$(head -n1 | md5sum)
Here, head will read one line (the password) and pass it to md5sum which will convert it to a hash. This hash can be compared with the known correct hash for this user's password. The text returned by head will be exactly what the user typed, unmangled by the shell.
Actually, for a known hash algorithm, it is possible to make a reverse look-up table for common passwords. So, the solution is to create a variable, called salt, that has some user dependent information:
var=$( { head -n1; echo "$salt"; } | md5sum)
The salt does not have to be kept secret. It is just there to make look-up tables more difficult to compute.
The md5sum algorithm, however, has been found to have some weaknesses. So, it should be replaced with more recent hash algorithms. As I write, that would probably be a sha-2 variant.
Again, if this is a sensitive application, do not use home-made tools
Answer to original question
how do I capture that within single quotes ('') when I can't state variables inside it like Var='$1'
The answer is that you don't need to. Consider, for example, this script:
#!/bin/sh
var=$1
echo $var
First, note that $$ and $? are both shell variables:
$ echo $$ $?
28712 0
Now, let's try our script:
$ bash ./script.sh '$$ $?'
$$ $?
These variables were not expanded because (1) when they appeared on the command line, they were in single-quotes, and (2) in the script, they were assigned to variables and bash does not expand variables recursively. In other words, on the line echo $var, bash will expand $var to get $$ $? but there it stops. It does not expand what was in var.
You can escape any dollar signs in a double-quoted string that are not meant to introduce a parameter expansion.
var=foo
# Pass the literal string fnSw38h$?2foo to script.sh
script.sh "fnSw38h\$?2$var"
You cannot do what you are trying to do. What is entered on the command line (such as the arguments to your script) must be in shell syntax, and will be interpreted by the shell (according to the shell's rules) before being handed to your script.
When someone runs the command script.sh fnSw38h$?2, the shell parses the argument as the text "fnSw38h", followed by $? which means "substitute the exit status of the last command here", followed by "2". So the shell does as it's been told, it substitutes the exit status of the last command, then hands the result of that to your script.
Your script never receives "fnSw38h$?2", and cannot recover the argument in that form. It receives something like "fnSw38h02" or "fnSw38h12", because that's what the user asked the shell to pass it. That might not be what the user wanted to pass it, but as I said, the command must be in shell syntax, and in shell syntax an unescaped and unnquoted $? means "substitute the last exit status here".
If the user wants to pass "$?" as part of the argument, they must escape or single-quote it on the command line. Period.

Resources