read input from stdin but not have it echo [duplicate] - bash

I'm trying to interpret this block of code. Searched google to see what these commands mean and no luck. I put my interpretation of what each line/block means to me. If I am wrong, please correct me. I am new to unix commands. Code:
#!/bin/bash
# input 1st command line argument for the version.
export VERSION=$1
# if user didn't input a version, print the echo message and exit (not sure what -n means but I am assuming)
if [[ ! -n "$VERSION" ]]; then
echo "Missing Version"
exit 1
fi
# creating variable UNAME that tells who the person is (their name)
export UNAME='whoami'
# no idea what -s and -p mean but i think this prints the message "enter password for $UNAME" and stores it in a new variable named PASSWORD. the $UNAME will print whatever whoami said.
read -s -p "Enter password for $UNAME: " PASSWORD
echo ""

The -p flag issues a prompt before reading input into a variable
The -s flag stop the typed response from being shown (i.e. for a sensitive password)
More information is available here:
https://linuxhint.com/bash_read_command/

-p
prompt output the string PROMPT without a trailing newline before
attempting to read.
-s
do not echo input coming from a terminal.

Related

Bash script stderr and stdout

First, i'm not sure if i'm formulating the question correctly.
So I have a bash script that delete a user in a system. The problem is, it is showing the err msg before the terminal code.
Here.
Code:
echo -e "\nCommencing user $UserName removal"
echo -e "Deactivating $UserName shell account ..." "$(chsh -s /usr/bin/false $UserName)"
echo -e "Listing group(s) user: " "$(groups $UserName)"
echo -e "Removing Crontab ... " "$(crontab -r -u $UserName)"
Here is the output:
Commencing user Test2 removal
chsh: unknown user: Test2
Deactivating Test2 shell account ...
groups: Test2: no such user
Listing group(s) user:
scripts/sudo.sh: line 332: /delete_Test2/crontab.bak: No such file or directory
Saving Crontab ...
The user to delete is Test2, which is this case "supposedly" does not exist (a different question for a different time). Now, shouldn't the stderr msg display next to the command or below it instead of above it?
Thanks in advance
When the shell executes the line echo -e "Deactivating $UserName shell account ..." "$(chsh -s /usr/bin/false $UserName)", here's the sequence of events:
The shell runs chsh -s /usr/bin/false Test2, with its stdout going to a capture buffer so the shell can use it later.
chsh discover's that "Test2" doesn't exist, and prints "chsh: unknown user: Test2" to its stderr. Since the shell didn't do anything special with its stderr, this goes directly to the shell's stderr, which is your terminal.
chsh exits
The shell takes the captured output (note: stdout, not stderr) from chsh (there wasn't any), and substitutes it into the echo command line, giving echo -e "Deactivating Test2 shell account ..." ""
Note that the error message gets printed at step 2, but the message about what's supposedly about to happen doesn't get printed until step 4.
There are several ways to solve this; generally the best is to avoid the whole mess of capturing the command's output, then echoing it. It's pointless, and just leads to confusions like this (and some others you haven't run into). Just run the command directly, and let its output (both stdout and stderr) go to their normal places, in normal order.
BTW, I also recommend avoiding echo -e, or indeed echo -anything. The POSIX standard for echo says "Implementations shall not support any options." In fact, some implementations do support options; others just treat them as part of the string to be printed. Also, some interpret escape sequences (like \n) in the strings to print, some don't, and some only do if -e is specified (violating the POSIX standard). Given how unpredictable these features are, it's best to just avoid such iffy situations, and either use printf instead (which is more complicated to use, but much more predictable), or (as in your case) just use a separate echo command for each line.
Also, you should almost always double-quote variable references (e.g. groups "$UserName" instead of groups $UserName), just in case they contain spaces, wildcards, etc.
Based on the above, here's how I'd write the script:
echo # print a blank line
echo "Commencing user $UserName removal"
echo "Deactivating $UserName shell account ..."
chsh -s /usr/bin/false "$UserName"
echo "Listing group(s) user: "
groups "$UserName"
echo "Removing Crontab ... "
crontab -r -u "$UserName"
Now, shouldn't the stderr msg display next to the command or below it instead of above it?
No, because the command is executed first and then its output is echoed. Perform the echo and command in two separate lines.
That's because chsh is writing to stderr before bash has a chance to write to stdout. Redirect stderr to stdout to get it right:
echo -e "Deactivating $UserName shell account ..." "$(chsh -s /usr/bin/false $UserName 2>&1)"
In general, it is better to check if the user exists before trying to deactivate it.

How to get values back from an external function that requires interactivity?

One of the routines I frequently use is a check for valid arguments passed when invoking scripts. Ideally, I'd like to make these, and other, similar, routines external functions that I could call from any script, for handling these more trivial processes. But, I'm having trouble retrieving the values I need from said function(s), without making the process more complicated.
I have tried using command substitution (e.g., echoing the output of the external function into a variable name local to the calling script), which seems to at least work with simpler functions. However, working with this file checking function, requires the read command in a loop, and, thus, user interactivity, which causes the script to hang when trying to resolve the variable that function call is stored in:
#!/bin/bash
# This is a simple function I want to call from other scripts.
exist(){
# If the first parameter passed is not a directory, then the input is
#+ invalid.
if [ ! -d "$1" ]; then
# Rename $1, so we can manipulate its value.
userDir="$1"
# Ask the user for new input while his input is invalid.
while [ ! -d "$userDir" ]; do
echo "\"$userDir\" does not exist."
echo "Enter the path to the directory: "
read userDir
# Convert any tildes in the variable b/c the shell didn't get to
#+ perform expansion.
userDir=`echo "$userDir" | sed "s|~|$HOME|"`
done
fi
}
exist "$1"
How can I retrieve the value of userDir in the calling script without adding (much) complexity?
You can have the exist function interact with the user over stderr and still capture the variable with command substitution. Let's take a simplified example:
exist() { read -u2 -p "Enter dir: " dir; echo "$dir"; }
The option -u2 tells read to use file descriptor 2 (stderr) for interacting with the user. This will continue to work even if stdout has been redirected via command substitution. The option -p "Enter dir: " allows read to set the prompt and capture the user input in one command.
As an example of how it works:
$ d=$(exist)
Enter dir: SomeDirectory
$ echo "$d"
SomeDirectory
Complete example
exist() {
local dir="$1"
while [ ! -d "$dir" ]; do
echo "'$dir' is not a directory." >&2
read -u2 -p "Enter the path to the directory: " dir
dir="${dir/\~/$HOME}"
done
echo "$dir"
}
As an example of this in use:
$ d=$(exist /asdf)
'/asdf' is not a directory.
Enter the path to the directory: /tmp
$ echo "new directory=$d"
new directory=/tmp
Notes:
There is no need for an if statement and a while loop. The while is sufficient on its own.
Single quotes can be put in double-quoted strings without escapes. So, if we write the error message as "'$dir' is not a directory.", escapes are not needed.
All shell variables should be double-quoted unless one wants them to be subject to word splitting and pathname expansion.
Right off the bat I'd say you can 'echo' to the user on stderr and echo your intended answer on stdout.
I had to rearrange a bit to get it working, but this is tested:
exist(){
# If the first parameter passed is not a directory, then the input is
#+ invalid.
userDir="$1"
if [ ! -d "$userDir" ]; then
# Ask the user for new input while his input is invalid.
while [ ! -d "$userDir" ]; do
>&2 echo "\"$userDir\" does not exist."
>&2 echo "Enter the path to the directory: "
read userDir
done
else
>&2 echo "'$1' is indeed a directory"
fi
echo "$userDir"
}
When I tested, I saved that to a file called exist.inc.func
Then I wrote another script that uses it like this:
#!/bin/sh
source ./exist.inc.func
#Should work with no input:
varInCallingProg=$(exist /root)
echo "Got back $varInCallingProg"
#Should work after you correct it interactively:
varInCallingProg2=$(exist /probablyNotAdirOnYourSystem )
echo "Got back $varInCallingProg2"

Bash user input while not match variable loop

I'm trying to get a user input to loop until the input/name is unique (not contained in output/variable).
I've tried to do something like this, which I thought would have worked:
read -p "$QlabelName" input
while [[ "$input" == "$(/usr/sbin/networksetup -listallnetworkservices |grep "$input")" ]]; do
read -p "Name already in use, please enter a unique name:" input
done
I've also tried putting the $(/usr/sbin/networksetup -listallnetworkservices |grep "$input") bit into a variable itself and then using the condition [[ "$input" == "GREPVARIABLE" ]] without success.
Original user input menu, without loop (working):
labelName=NJDC
QlabelName=$(echo Please enter the name of connection to be displayed from within the GUI [$labelName]: )
read -p "$QlabelName" input
labelName="${input:-$labelName}"
echo "The connection name will be set to: '$labelName'"
I've tried a variety of solutions from SO, Unix, ServerFault, etc with no success. I've tried if, while, until, !=, ==, =~ as well with no success.
I've confirmed with simple debug echo's at each step that variables contain the data, but the loop is not working.
EDIT (solution, in context to the question, thanks to #LinuxDisciple's answer):
labelName=NJDC
QlabelName=$(echo Please enter the name of connection to be displayed from within the GUI [$labelName]: )
read -p "$QlabelName" input
while /usr/sbin/networksetup -listallnetworkservices |grep -q "^${input}$"; do
read -p "Name already in use, please enter a unique name:" input
done
labelName="${input:-$labelName}"
echo "The connection name will be set to: '$labelName'"
This was important to me to keep default variable values for labelName and output the correct information to the user.
read -p "$QlabelName" input
while /usr/sbin/networksetup -listallnetworkservices |grep -q "^${input}$"; do
read -p "Name already in use, please enter a unique name:" input
done
grep's return code is good enough for while, and since we don't want to actually see the output, we can use -q to suppress it. You can also run it without -q to see what grep actually found until you're satisfied that it's running correctly.
For further debuggability, I would pipe the output to cat -A. You can echo your variable value in the while-loop and just add |cat -A immediately after the done and it should show all the characters:
read -p "$QlabelName" input
while /usr/sbin/networksetup -listallnetworkservices |grep -q "^${input}$"; do
read -p "Name already in use, please enter a unique name:" input
echo "Input was:'$input'"
done |cat -A

In bash, how to process all user input on command line

How to make all user input on command line as stdin for a program?
In my case, I want to replace certain words inputed by user. For example, every time user uses the word animal1, I want it received as goldfish. So it would look like this:
$ animal1
goldfish: command not found
I tried the following bash command
while read input
do
sed "s/animal2/zebra/g;s/animal1/goldfish/g" <<< "$input"
done
But it prompts for user input and does not return to bash. I want it to run while using bash command line.
Also, this allowed me to capture output only.
bash | sed 's/animal2/zebra/g;s/animal1/goldfish/g'
But not user input.
If I understand you correctly, sounds like you just need to set up some aliases:
$ alias animal1=goldfish
$ animal1
bash: goldfish: command not found
This allows the shell to be used interactively as usual but will make the substitutions you want.
You can add this alias definition to one of your startup files, commonly ~/.bashrc or ~/.profile, to have them take effect on any new shell that you open.
The solution provided by Tom Fenech is good, however, if you plan to add more features to the command you can use a function like the following:
animal1() {
echo "Welcome to the new user interface!"
goldfish
# other commands
}
and put it in the user ~/.bashrc or ~/.bash_profile
The output will be:
$>animal1
Welcome to the new user interface!
-bash: goldfish: command not found
By using this approach you can, for example, create a custom output message. In the following snippet I take the return vale from the command and process it word by word. Then I remove the -bash: part of the output and reconstruct the message and output it.
animal1() {
echo "Welcome to the new user interface!"
retval=$(goldfish 2>&1)
# Now retval stores the output of the command glodfish (both stdout and stderr)
# we can give it directly to the user
echo "Default return value"
echo "$retval"
echo
# or test the return value to do something
# here I build a custom message by removing the bash part
message=""
read -ra flds <<< "$retval"
for word in "${flds[#]}" #extract substring from the line
do
# remove bash
msg="$(echo "$word" | grep -v bash)"
# append each word to message
[[ msg ]] && message="$message $msg"
done
echo "Custom message"
echo "$message"
echo
}
Now the output would:
Welcome to the new user interface!
Default return value
-bash: goldfish: command not found
Custom message
goldfish: command not found
If you comment the lines that echoes the default return value then you get exactly the output you asked for.

How do I search a txt file for names from command line or interactive command line

I am trying to make a script that will either take command line input after or if user input is null then an interactive prompt will as for a name to be used to search a text file. I'm using bash and gedit.
I want it to go like this:
./scriptName input
if inupt is null
echo Please enter a name.
I'm just getting started and I cannot figure out how to get this first part to function.
#!/bin/bash
name="$1"
if [[ "$name" == "" ]]; then
read -p "Please enter a name:" name
fi
# continue

Resources