sentence as user input - multiple times from terminal - bash script - bash

I am trying to send lines from terminal to a text file multiple times using the following script. After writing the first line and its description in 2nd line, the script asks user whether he wants to enter another line or not. If yes, then user writes the 3rd line, 4th line and so on...
my problem is that after 2nd line, i.e. starting from 3rd line, the script writes only the first word, not the full sentence. How do I solve this ?
function ml() {
echo $# >> $HOME/path/to/file/filename
echo -n "Enter description and press [ENTER]: "
read description
echo -e '\n[\t]' $description >> $HOME/path/to/file/myfile
while true
do
read -p "Add another line?y?n" -n 1 -r
echo -e "\n"
if [[ $REPLY =~ ^[Yy]$ ]]
then
echo -n "Enter another line and press [ENTER]: "
read -a meaning
echo -e "[\t]" $meaning >> $HOME/path/to/file/myfile
else
break
fi
done
echo % >> $HOME/path/to/file/myfile
}
also I would like to have another modification in the code
read -p "Add another line?y?n" -n 1 -r
instead of asking y/n input, can it be done that after inserting the first two line, every ENTER will ask for another line input and pressing ESCAPE will terminate the script?

This is because in your second call to read, you are using the -a argument which does:
The words are assigned to sequential indices of the array variable aname, starting at 0. aname is unset before any new values are assigned. Other name arguments are ignored.
That appears to be not what you want.

Related

Bash: Is there a way to require the enter key to be pushed with read -n once character limit is reached?

Is there a way to require the enter key to be pushed with read -n once character limit is reached, rather than automatically jumping to the following line in the script?
For example, in the following script:
echo "Please enter your credentials below"
read -p "Please enter your username: " -e -n 15 usern
read -p "Please enter your password: " -s -e -n 15 passw
Once 15 chars are entered for the username, it automatically jumps to the password prompt. However, it would be more user-friendly to simply stop allowing input once the 15 chars are reached (besides the backspace key and enter key), and require the user to press enter to continue to the password prompt. This is how most logins work, after all...
I'm aware that I could use a while loop/if statement restricting the char limit in the usern variable (e.g. with -gt), but I was wondering how I could specifically limit the user to pressing either backspace or enter (or even the arrow keys if they want to edit a single character from their username, but I'm not worried about this right now), once those 15 characters are reached, and REQUIRE the user to press enter to continue to the following prompt.
Hopefully what I am asking for makes sense. Thanks to everyone in advance!
As mentioned in the comments, bash's read isn't able to reproduce the expected behaviour for specific keys. However, you can avoid using read by rolling your own input parsing.
Some points to take into account:
Terminal line settings need to be adjusted with stty, to avoid printing input characters as they are typed (i.e. we only want to print our messages, otherwise characters would be duplicated);
Terminal emulator needs to support VT100 escape codes, in order to erase the line and move the cursor when redrawing the prompt;
Characters can be compared by ascii code, but when enter is pressed, we don't get a read character for the newline itself. However, since other whitespace characters are read, this is not an issue.
The following script implements the expected behaviour, by accepting characters up to a given limit (for the password, no typed characters are printed). The input is only submitted when enter is pressed.
#!/bin/sh
set -eu
parse() {
message=$1
var=$2
is_password=$3
input=
stty -icanon -echo
while true; do
# Clear line, move cursor to beginning, then print prompt
if [ "$is_password" -eq 1 ]; then
printf '\33[2K\r'"$message "
else
printf '\33[2K\r'"$message $input"
fi
# Read 1 character
i=$(dd bs=1 count=1 2>/dev/null)
# Was a backspace read?
if echo "$i" | grep -qP '\x7f'; then
# Remove last character
input=$(echo "$input" | sed 's/.$//')
# Only add read character if input field limit wasn't reached
elif [ ${#input} -lt 15 ]; then
input=$input$i
fi
# Was a newline read?
if [ -z "$i" ]; then
break
fi
done
stty icanon echo
eval "$var=$input"
}
echo "Please enter your credentials below"
parse "Please enter your username:" "usern" 0
printf "\n%s\n" "Read username: $usern"
parse "Please enter your password:" "userp" 1
printf "\n%s\n" "Read password: $userp"

Indenting "read" input when it contains multiple lines

I have a read command in a bash script whose input defines an array. The input will frequently be copy/pasted data that contains multiple lines. Each line in the multi-line input is correctly captured and added to the array as separate elements, but I'd like to indent each line with a > prefix in the terminal window when it is pasted in.
This is for bash v3 running on macOS. I've attempted various flavors of the read command, but couldn't come across anything that worked.
Script:
#!/bin/bash
echo "Provide inputs:"
until [[ "$message" = "three" ]]; do
read -p "> " message
myArray+=($message) #Input added to array for later processing
done
Manually typed inputs look like this:
Provide inputs:
> one
> two
> three
But a copy/pasted multi-line input look like this:
Provide inputs:
> one
two
three
> >
The desired result is for the copy/pasted multi-line input to look identical to the manually entered inputs.
It sounds like the issue is with the way read works. Read echos back keystrokes and I think perhaps because of stdout buffer it is writtern before the echo statements are flushed.
Using a combo of echo command and the -e argument to read (interactive) fixes this in my testing.
#!/bin/bash
echo "Provide inputs:"
until [[ "$message" = "three" ]]; do
echo -ne "> "
read -e message
myArray+=($message) #Input added to array for later processing
done
(Answer changed after explanation of OP what he wants)
The screen will look identical when the input is entered line-by-line and when entered after copy-paste of multiple lines when you remove the > (I added -r in view of special characters).
until [[ "$message" = "three" ]]; do
read -r message
myArray+=("$message")
done
When you want to see the >, you can use the ugly
printf "> "
until [[ "$message" = "three" ]]; do
read -rs message
printf "%s\n> " "${message}"
myArray+=("$message")
done
In this case the input is only shown after an Enter, so this seems worse.

Adding new lines to multiple files

I need to add new lines with specific information to one or multiple files at the same time.
I tried to automate this task using the following script:
for i in /apps/data/FILE*
do
echo "nice weather 20190830 friday" >> $i
done
It does the job yet I wish I can automate it more and let the script ask me for to provide the file name and the line I want to add.
I expect the output to be like
enter file name : file01
enter line to add : IWISHIKNOW HOWTODOTHAT
Thank you everyone.
In order to read user input you can use
read user_input_file
read user_input_text
read user_input_line
You can print before the question as you like with echo -n:
echo -n "enter file name : "
read user_input_file
echo -n "enter line to add : "
read user_input_text
echo -n "enter line position : "
read user_input_line
In order to add line at the desired position you can "play" with head and tail
head -n $[$user_input_line - 1] $user_input_file > $new_file
echo $user_input_text >> $new_file
tail -n +$user_input_line $user_input_file >> $new_file
Requiring interactive input is horrible for automation. Make a command which accepts a message and a list of files to append to as command-line arguments instead.
#!/bin/sh
msg="$1"
shift
echo "$msg" | tee -a "$#"
Usage:
scriptname "today is a nice day" file1 file2 file3
The benefits for interactive use are obvious -- you get to use your shell's history mechanism and filename completion (usually bound to tab) but also it's much easier to build more complicated scripts on top of this one further on.
The design to put the message in the first command-line argument is baffling to newcomers, but allows for a very simple overall design where "the other arguments" (zero or more) are the files you want to manipulate. See how grep has this design, and sed, and many many other standard Unix commands.
You can use read statement to prompt for input,
read does make your script generic, but if you wish to automate it then you have to have an accompanying expect script to provide inputs to the read statement.
Instead you can take in arguments to the script which helps you in automation.. No prompting...
#!/usr/bin/env bash
[[ $# -ne 2 ]] && echo "print usage here" && exit 1
file=$1 && shift
con=$1
for i in `ls $file`
do
echo $con >> $i
done
To use:
./script.sh "<filename>" "<content>"
The quotes are important for the content so that the spaces in the content are considered to be part of it. For filenames use quotes so that the shell does not expand them before calling the script.
Example: ./script.sh "file*" "samdhaskdnf asdfjhasdf"

Loop skipping first line of text file being read, working for second line, but also not reading third line

I have a text file with different usernames being read into a loop in Bash. Each one of these is on its own line. I want to perform the actions within this loop for each username in the text file. I think the issue may have to do with the fact I'm calling another script but I'm not sure. Here's an example of the text file
bobsmith123
jimjenkins456
susanjones789
I'm then grabbing some information from AD by using an LDAP search and then passing this information to another script by use of env variables. However, the first line of the file is skipped entirely, the second line works correctly, and the third line seems to not read the username from the text file but performs all of the actions, resulting in an error in the script being called as $CENTRIFY_USER is empty. Any help is appreciated. Here is my script.
echo "Enter username for LDAP Search"
read USERNAME
export USERNAME
echo "Enter password"
read -s PASSWORD
export PASSWORD
echo "What user do you want to add to Centrify?"
while IFS='' read -r line || [[ -n "$line" ]]; do
read CENTRIFY_USER
export CENTRIFY_USER
OBJECTSID=`ldapsearch -H ldap://my.domain.com:389 -D "$USERNAME#MY.DOMAIN.COM" -w $PASSWORD -x -b "DC=my,DC=domain,DC=com" "(&(objectCategory=user)(sAMAccountName=$CENTRIFY_USER))" | grep objectSid | cut -d " " -f2`
SID=`/home/myhome/convert_objectSid_to_sid.sh $OBJECTSID`
export SID
echo "Adding user to Centrify..."
/home/myhome/add_users_to_centrify.sh
done < centrify_users_to_add.txt
In your while loop, you do two reads. The first is read -r line reads the first line then read CENTRIFY_USER which is the second line. You do not appear to be using $line in you script.
You appear to need:
while IFS='' read -r CENTRIFY_USER; do
export CENTRIFY_USER
....
done < centrify_users_to_add.txt

How to just input 'y' without having to press ENTER?

Using this portion of a bash script as an example
{
read -p "Do you want to update the tv feed? [y/n/q] " ynq
case $ynq in
[Yy]* ) rm ~/cron/beeb.txt; /usr/bin/get-iplayer --type tv>>~/cron/beeb.txt;;
[Nn]* ) echo;;
[Qq]* ) exit;;
* ) echo "Please answer yes or no. ";;
esac
}
How do I get it so that you can press y and not have to press Enter for it to be accepted please?
Add -n 1 to the read command's options. From the bash manpage:
-n nchars
read returns after reading nchars characters rather than
waiting for a complete line of input.
BTW, you should also double-quote "$ynq" -- sometimes users will just press return, which can cause weird behavior if the variable isn't double-quoted. Also, note that read -n is a bash extension, so make sure you're using bash (i.e. #!/bin/bash or similar for the first line of the script), not a brand-x shell (#!/bin/sh or similar).
Use -n1 with read to specify max number of input length to 1:
read -n1 -p "Do you want to update the tv feed? [y/n/q] " ynq
I am on Mac and using read -n1 $user_decision doesn't do the trick for some reason in bash, sh, or zsh. So, I am using this which works across all:
#!/bin/zsh
# -k1 = First char pressed without waiting, for /r or /n.
# -t3 = timeout for 3 seconds
# -s = prevent outputting the input back to stdout.
echo "Press any letter..."
read -t3 -k1 -s user_decision
# Prints 1st arbitrary keypress entered during 3s timeout
echo $user_decision # EG: "y", or "n" for instance.
To simplify, you can just use read -k1 user_decision to get precisely what you requested set into the value for the variable name $user_decision, without waiting for /r or /n (hitting enter or return).

Resources