Bash <read> printing new lines, how to prevent - bash

So, I have a situation where I want to print output onto the output that was just posted by a user, and I wrote this short thing to make sure it's possible.
echo -e "Cmd> \c" && read cmd && echo "-append_something"
Although it would seem that read prints a new line, on "enter". Is there a way to cancel this? Or any other way I could print this output onto the same line.
Seeing input in real-time IS necessary.
Expected output: Cmd> <whatever>-append_something

Use -s option to read:
echo -e "Cmd> \c" && read -s cmd && echo "-append_something"
Update:
Using classic ANSI screen cursor manipulation should do the trick:
echo -e "Cmd> \c"$'\e[s' && read cmd && echo $'\e[u'"${cmd}-append_something"
Simpler:
echo -ne "Cmd> \e[s" && read cmd && echo $'\e[u'"${cmd}-append_something"
Another shorter:
read -p "Cmd> "$'\e[s' cmd && echo $'\e[u'"${cmd}-append_something"
Something even more manual:
read -p "Cmd> " cmd && echo $'\e[A\e[5C'"${cmd}-append_something"

Related

How to make one loop out of five in bash?

I have a small script for Mac where I'm adding printers. It works fine but I think I could make it simpler or at least it would be interesting to know a different solution.
while IFS= read -r line;
do
if [[ $line == *"Printer_E1"* ]]
then
if [[ "$FIND_PRINTERS" =~ "$PRINTER_E1_IP" ]];
then
echo "found printer e1"
else
echo "adding printer e1"
"$LPADMIN" -p "$PRINTER_E1_IP" -v "lpd://$PRINTER_E1_IP" -L "$PRINTER_E1_LOCATION" -P "$PRINTER_E1_PPD" -E -o printer-is-shared=false -D "$PRINTER_E1_NAME"
echo "adding printer e1 done"
fi
fi
done <<< "$AD_GROUPS"
The content of $AD_GROUPS is:
Printer_E0
Printer_E1
Printer_E2
Printer_E3
Printer_E4
Printer_Strasse
Printer_Wien
I have such a loop for 5 printers, so 5 times that just with different variables.
How could I do that with one loop? (or how can I make that different or simpler)?
Something like this:
while IFS= read -r printer; do
[[ "$FIND_PRINTERS" =~ "${printer}_IP" ]] && \
echo "Found ${printer}" && continue
echo "Adding ${printer}..."
"$LPADMIN" -p "${printer}_IP" \
-v "lpd://${printer}_IP" \
-L "${printer}_LOCATION" \
-P "${printer}_PPD" -E -o printer-is-shared=false \
-D "${printer}_NAME" \
&& echo "Done"
done <<< "$AD_GROUPS"
I assume your variable FIND_PRINTERS has some printers you want to skip, that you have already set the parameters (IP, LOCATION etc) related to each printer.
We use the variable inside double quotes into there, so it expands to what you want for the various commands. Also I have simplified the if condition then command to condition && command and also continue moves to next iteration.

BLE gatttool interactive shell script

I established a connection with a BLE device using gatttool. First I connected to the device with sudo gatttool -t random -b FF:3C:8F:22:C9:C8 -I and connect. After that I read the value of specific characteristic with char-read-uuid 2d30c082-f39f-4ce6-923f-3484ea480596.
What I want to do is to automate the whole process and put the latter command (querying for value) in the loop, ideally saving each value (appending) to a text file. I tried something like
sudo gatttool -t random -b FF:3C:8F:22:C9:C8 -I <<EOF
connect
while[ 1 ]; do
char-read-uuid 2d30c082-f39f-4ce6-923f-3484ea480596 > output.txt
done
exit 1
EOF
but it does not help, since I am not even able to connect to the device (ideally there should be some delay between the first and the second command). Also after connecting, an interactive mode is enabled and the shell commands do not work there. I'd appreciate any clues on how to tackle this issue.
If gattool writes prompts to stdout (and doesn't suppress them given non-TTY file descriptors), consider something like:
#!/usr/bin/env bash
case $BASH_VERSION in ''|[123].*|4.0.*) echo "ERROR: bash 4.1 or newer required" >&2; exit 1;; esac
exec {output_fd}>output.txt
prompt_re='[>] '
capture_re='^handle:.*value:.*$'
wait_for_prompt() {
IFS= read -r line || return
while ! [[ $line =~ $prompt_re ]]; do
[[ $line =~ $capture_re ]] && printf '%s\n' "$line" >&$output_fd
IFS= read -r line || return
done
}
wait_for_prompt
echo connect
while wait_for_prompt; do
echo "char-read-uuid 2d30c082-f39f-4ce6-923f-3484ea480596"
done
...saved as yourscript, and invoked using socat as:
socat 'SYSTEM:sudo gatttool -t random -b FF:3C:8F:22:C9:C8 -I 2>&1' 'EXEC:./yourscript'
(assuming that sudo is configured to work without a TTY; otherwise, you might move it to be sudo socat).
Indeed, pexpect works fine here. You can find my solution below. The code reads the value of the specific UUID, which contains IMU readings (floats).
import pexpect
import struct
import time
import sys
IMU_MAC_ADDRESS = "FF:3C:8F:22:C9:C8"
UUID_DATA = "2d30c082-f39f-4ce6-923f-3484ea480596"
if __name__ == '__main__':
gatt = pexpect.spawn("gatttool -t random -b " + IMU_MAC_ADDRESS + " -I")
gatt.sendline("connect")
gatt.expect("Connection successful")
while(True):
gatt.sendline("char-read-uuid " + UUID_DATA)
gatt.expect("handle: 0x0011 value: ")
gatt.expect(" \r\n")
data = (gatt.before).decode('UTF-8').replace(" ", "").decode('hex')
print(struct.unpack('f', data)[0]

Is there a Bash wrapper (program/script) that enables a more succinct input when I want multiple outputs in one Bash call

I'm currently creating monstrosities like the following:
ll /home && echo -e "==============\n" && getent passwd && echo -e "==============\n" && ll /opt/tomcat/ && echo -e "==============\n" && ll /etc/sudoers.d/
Is there perhaps some program that handles this in a nicer way?
Something like this (the hypothetical name of the program would be multiprint in my example):
multiprint --delim-escapechars true --delim "============\n" '{ll /home},{getent passwd},...'
alternatively:
multiprint -de "============\n" '{ll /home},{getent passwd},...'
A function like the following would give you that ability :
function intersect() {
delim=$1
shift
for f; do cat "$f"; echo "$delim"; done
}
You could call it as follows to implement your specific use-case :
intersect '==============' <(ll /home) <(getent passwd) <(ll /opt/tomcat/) <(ll /etc/sudoers.d/)
You can try it here.
printf will repeat its format until its arguments are exhausted. You could write something like
printf '%s\n================\n' "$(ll /home)" "$(getent passed)" "$(ll /opt/tomcat)" "$(ll /etc/sudoers.d)"
although this is a little memory-intensive, since it buffers all the output in memory until all the commands have completed.
Based on #Aaron's answer I ended up creating this multiprint.sh Bash shell script, and for what it's worth posting it here:
#!/bin/bash
# Print output of multiple commands, delimited by a specified string
function multiprint() {
if [[ -z "$*" ]]; then
__multiprint_usage
return 0
elif [[ "$1" == "--help" ]]; then
__multiprint_usage
return 0
else
delim=$1
shift
for f; do cat "$f"; echo -e "$delim"; done
fi
}
function __multiprint_usage() {
echo "Usage:"
echo " multiprint '<delimiter>' <(cmd1) <(cmd2) ..."
# example: multiprint '\n\n\n' <(ll /home/) <(ll /var/) ..."
}

How to pass value to read variable with pipe (stdin) in bash

I read a lot about passing piping stdin to bash read function, but nothing seems to work for my bash version!!
GNU bash, version 3.2.51(1)-release (x86_64-suse-linux-gnu)
I have a bash script that in some point asks the user "yes/no" with variable CONTINUEQUESTION:
echo "Do you want to continue? (yes/no):"
read CONTINUEQUESTION
tmp=$(tr '[:upper:]' '[:lower:]' <<<$CONTINUEQUESTION)
if [[ "$tmp" != 'y' && "$tmp" != 'yes' ]]; then
echo "Aborting because of input '$CONTINUEQUESTION'"
exit
fi
I would like to pipe a "yes or no" to this question without user input!
Yes i know i could use expect, but i don't prefer it in this case.
So i tried several things:
CONTINUEQUESTION='yes'
echo $CONTINUEQUESTION | ./myscript.sh
Aborting because of input ''
./myscript.sh <<< "$CONTINUEQUESTION"
Aborting because of input ''
...and many other, nothing worked!?
O.k. now I did a bit revers thinking and find out that the below line causes the problem with the pipe...because when i remarked it out all the below answers are working just fine, but not when this line is executed:
running=`ssh root#${HOSTNAME} 'su - root -c "/bin/tools list | grep \"system running\"" 2>&1'`
But, i need this line before the read! What do i need to reverse the 2>&1????
My script look like this and is working without this try to over come the user intervantion:
LIST_FILE_NAME=$1
STILL_RUNNING=0
running=`ssh root#${HOSTNAME} 'su - root -c "cat '$LIST_FILE_NAME' | grep \"system running\"" 2>&1'`
if [[ $running =~ .*running.* ]]; then
STILL_RUNNING=1
echo "NODE $NODE running stop before continuing."
fi
if [ $STILL_RUNNING -eq 1 ]; then
echo "Aborting system was still running!"
exit 1
fi
echo "Do you want to continue? (yes/no):"
read CONTINUEQUESTION
tmp=$(tr '[:upper:]' '[:lower:]' <<<$CONTINUEQUESTION)
if [[ "$tmp" != 'y' && "$tmp" != 'yes' ]]; then
echo "Aborting because of input '$CONTINUEQUESTION'"
exit
fi
echo "o.k."
4 points:
list.log can have a line with "system running" or "system notrunning"
if list.log has a line with "system notrunning" than the bash script continue towards the question
at the question i never got it right to inject the 'y' or 'yes' so the bash aborts because of input ''
i execute this like: ./myscript.sh list list.log (normal way)
This bash runs well if the user interacts at the question!
Thanks for you time!!!
Consider this variation as well:
#!/bin/bash
read -p "Do you want to continue? (yes/no): " CONTINUEQUESTION
if [[ $CONTINUEQUESTION != [Yy] && $CONTINUEQUESTION != [Yy][Ee][Ss] ]]; then
echo "Aborting because of input '$CONTINUEQUESTION'."
exit
fi
Tested with:
bash script.sh <<< yes
If it doesn't work, show the output of:
bash -x script.sh <<< yes
Your line
$CONTINUEQUESTION='yes'
shoul really be
CONTINUEQUESTION='yes'
I am not sure then that your are feeding stdin with the word 'yes'. You could add an echo after the read to be sure.
You can use heredoc:
bash -ex ./myscript.sh << 'EOF'
yes
EOF
Search for Here Documents in man bash.
EDIT: Based on comments you can use this ssh command:
running=$(ssh -t -t root#${HOSTNAME} "grep 'system running' \"$LIST_FILE_NAME\"")

define "$key" use it in a script, but also store it

How can I create a specific line in another file using bash please? Like
echo "Please input the days you want to keep "
$key= ?
touch .beebrc; keep="$key"
where the file ".beebrc" has a line 'keep= x' and "$key" is created in the main script.
But how do I define "$key" please? And write it into ".beebrc" as a new line at position/line 8? The full function is -
function trim {
echo;
read -t "$temi" -n1 -p ""$bldgrn" Do you want to delete some of your download history? [y/n/q/r] $(tput sgr0)" ynqr ;
case "$ynqr" in
[Yy]) echo
read -t "$temi" -n3 -p ""$bldgrn" Please input the days you want to keep $(tput sgr0)" key ## ask
if test -e .beebrc && grep -q "^keep=" .beebrc 2>/dev/null ; then
sed -i "s/^keep=.*/keep=$key/" .beebrc
else
echo "keep=$key" >> .beebrc
#fi
cd /home/$USER/.get_iplayer
eval "$player" --trim-history "$key"; cd; ques;
#echo;;
[Nn]) ques;;
[Qq]) endex;;
[Rr]) exec "$beeb";;
* ) echo ""$bldgrn" Thank you $(tput sgr0)";;
esac
fi
};
Does this help in defining it all? (Sorry, should've put it in at first)
Perhaps:
read -p "Please input the days you want to keep: " key ## Ask.
echo "keep=\"$key\"" > .beebrc ## Store.
Use read to capture user input into a variable, and then write it to your file.
For example:
echo "Please input the days you want to keep "
read key
echo $key > .beebrc
#!/bin/bash
read -p "Please input the days you want to keep: " key
if test -e .beebrc && grep -q "^keep=" .beebrc 2>/dev/null ; then
sed -i "s/^keep=.*/keep=$key/" .beebrc
else
echo "keep=$key" >> .beebrc
fi
This script:
Prompts for input and stores the value in $key
Tests if .beebrc exists and that a line beginning "keep=" exists in it. If so, replace the keep= line with keep=$key
Otherwise append a new line/create the file with keep=$key.
This will need validation added because user input should not be trusted. (this answer might help)

Resources