How to answer a bash arrow selection menu through script - bash

little stuck atm with figuring out how to script an answer to a bash arrow selection menu
Basically my question is how do I script selection of an item based on name?
I do know that I can use the following command to select an option by index and this is as close as I've gotten but I think there's a way to select it by name instead
echo 5 | zapier link
Important notes:
When a version is already selected it gets the [currently linked app] at the end of the version, this needs to be taken into account in text search
The numbers in () i.e (142900) is important
Pretend the name that's blocked out in red is testOver
I would prefer not to have to install any tools to accomplish this, only use whats built into bash unless there's no other option.
Thanks in advance for your help!

To select a menu entry by search, you have to read zapier's output. Usally this would be a job for expect, but since you don't want to install anything, try the following:
mkfifo fifo
zapier link <fifo |
awk '/Which integration/ {m=NR}
m && /\(142900\)/ {print NR-m; fflush(); m=0}' >fifo
rm fifo
This selects the option containing the string (142900). Of course you can adapt the regex to select any other option. Also consider replacing /Which integration/ by the full title of the menu to make detection safer.
If you want to see the output of zapier, use | tee >(awk ... >fifo).
In case zapier behaves differently when running inside a pipe, run it inside a pseudo-terminal. If zapier buffers its output for pipes, using script or stdbuf to unbuffer the output is an absolute requirement. Otherwise the entire command may run into a deadlock.
script -qec './zapier link' /dev/null <fifo # on Linux
script -qe /dev/null ./zapier link <fifo # on BSD/macOS.
Unfortunately, awk does not know when the menu is fully printed, so it cannot look at all the options first and then decide afterwards, but has to select one option as soon as it sees it. If there is no such option, the script gets stuck. To solve this you could replace awk with a small bash script:
zapier link <fifo | while IFS= read -r ln; do
[[ "$ln" = *"Which integration"* ]] || continue
IFS=$'\n' read -rd '' -t1 -a opts
# insert code to pick an option from the array `opts`
# either `echo "$((i+1))"` to select option `${opts[i]}`
# or `break` to select no option
done >fifo

Related

Monitor changes in stdin from a bash script

I would like to make a bash tool as a script to get notified when a change in the output of a given command happens. Use cases would be for instance to get a notification when the output of a long script changes. I would like it to be used on the same model as the tee command :
any_long_script_to_run | my_tool
The output would be then be transparently copied to stdout, but I would like to define as well a custom function (play a sound, display a notification...) to run each time a new line is written for instance.
Is there any clever way of doing this in bash ?
Thanks a lot !
The command to create a notification depends on what OS/window manager you're running, but one simple answer is:
any_long_running_command | while IFS= read -r line; do
printf "\a%s\n" "$line"
done
\a is the "bell" character, and usually makes a sound.

getting interactive, multiple line, formatted input from stdin in bash

I want to be able to interactively get output from the terminal in a way similar to a hereDOC. Ie I want the user to be able to type multiple lines, then have that information passed into a file with all the formatting maintained. Something like this.
echo "Type your message below. To finish the letter type DONE by itself on a line"
file=mktmp
cat << DONE > $file
obviously this doesn't work, because the EOF is found before DONE. I thought about passing the user to something like VIM, but my less computer savy coworkers have a hard time with vim/emacs/nano.
You need to use an editor; standard input is just a stream of bytes, not an editor. However, you don't have to hard-code a specific editor. EDITOR is a standard environment variable meant to allow your script's caller to choose which editor is used.
: ${EDITOR:?Please set the environment variable EDITOR to the editor of your choice}
echo "Type your message below, then save and exit your editor."
"$EDITOR" "$file"
EDITOR is typically set by the user in their shell configuration file, but can be set on-demand when you run your script.
$ EDITOR=nano yourScript.sh
okay, so I came up with this, but please help me find something better or improve on it.
echo "Type your message below, to finish the letter press CTL+D"
mapfile message
file=`mktemp`
for x in `seq 0 ${#message[#]}`
do printf "${message[$x]}" >> $file
done
cat $file

Is there any way to come back to a ready-to-enter command in fish shell by just pressing a combination of keys?

Some times that I have a command ready to press enter but that command I have changed it in some way and it's a long command, then I remember that I have to open a text file (e.g. to get some information that I will use in the command). So what most of the times I do, is to cancel that command (Ctrl+C) and then open the text file get the information I need and then retype the command again with the pasted value from the text file. This is not very efficient for me specially if the server doesn't have any kind of GUI and I can't copy the previous command so I don't lose it.
So my question is, Is there any kind of combination keys that I could use to save a command ready to enter so I don't lose it and I don't have to type it all over again?
Thanks!
This is currently not possible out of the box.
The easiest way to do it is probably to
Change the cancel binding to stash the commandline
Add a binding to recall the stashed commandline
It would work something like this:
The function to recall:
function recall_commandline
if set -q stashed_commandline
commandline -r -- $stashed_commandline
end
end
Add to __fish_cancel_commandline (use funced __fish_cancel_commandline. Once you are happy do funcsave __fish_cancel_commandline):
set -g stashed_commandline $cmd
# right before:
commandline ""
Add to fish_user_key_bindings
bind \cr recall_commandline
This will allow you to press Ctrl+r to recall the last cancelled commandline. Expanding it to multiple is non-trivial (since "commandlines" can have multiple lines), as is adding the commandlines to history so that they can be recalled with the normal bindings.
I have the following function to turn comment/uncomment the current statement:
function toggle-comment-cmd-buffer --description 'Comment/Uncomment the current or every line'
set -l cmdlines (commandline -b)
if test "$cmdlines" = ""
return
end
set -l cmdlines (printf '%s\n' '#'$cmdlines | string replace -r '^##' '')
commandline -r $cmdlines
string match -q '#*' $cmdlines[1]; and commandline -f execute
end
I bind it thusly: bind \e\# toggle-comment-cmd-buffer. This way I can quickly comment and put the current statement in my command history in order to do something else. Then I can recall the comment and press [alt-#] again to remove the comment characters and continue modifying the command.
I set this up in my personal fish config because I had gotten used to doing something similar in ksh93.

use "!" to execute commands with same parameter in a script

In a shell, I run following commands without problem,
ls -al
!ls
the second invocation to ls also list files with -al flag. However, when I put the above script to a bash script, complaints are thrown,
!ls, command not found.
how to realise the same effects in script?
You would need to turn on both command history and !-style history expansion in your script (both are off by default in non-interactive shells):
set -o history
set -o histexpand
The expanded command is also echoed to standard error, just like in an interactive shell. You can prevent that by turning on the histverify shell option (shopt -s histverify), but in a non-interactive shell, that seems to make the history expansion a null-op.
Well, I wanted to have this working as well, and I have to tell everybody that the set -o history ; set -o histexpand method will not work in bash 4.x. It's not meant to be used there, anyway, since there are better ways to accomplish this.
First of all, a rather trivial example, just wanting to execute history in a script:
(bash 4.x or higher ONLY)
#!/bin/bash -i
history
Short answer: it works!!
The spanking new -i option stands for interactive, and history will work. But for what purpose?
Quoting Michael H.'s comment from the OP:
"Although you can enable this, this is bad programming practice. It will make your scripts (...) hard to understand. There is a reason it is disabled by default. Why do you want to do this?"
Yes, why? What is the deeper sense of this?
Well, THERE IS, which I'm going to demonstrate in the follow-up section.
My history buffer has grown HUGE, while some of those lines are script one-liners, which I really would not want to retype every time. But sometimes, I also want to alter these lines a little, because I probably want to give a third parameter, whereas I had only needed two in total before.
So here's an ideal way of using the bash 4.0+ feature to invoke history:
$ history
(...)
<lots of lines>
(...)
1234 while IFS='whatever' read [[ $whatever -lt max ]]; do ... ; done < <(workfile.fil)
<25 more lines>
So 1234 from history is exactly the line we want. Surely, we could take the mouse and move there, chucking the whole line in the primary buffer? But we're on *NIX, so why can't we make our life a bit easier?
This is why I wrote the little script below. Again, this is for bash 4.0+ ONLY (but might be adapted for bash 3.x and older with the aforementioned set -o ... stuff...)
#!/bin/bash -i
[[ $1 == "" ]] || history | grep "^\s*$1" |
awk '{for (i=2; i<=NF; i++) printf $i" "}' | tr '\n' '\0'
If you save this as xselauto.sh for example, you may invoke
$ ./xselauto.sh 1234
and the contents of history line #1234 will be in your primary buffer, ready for re-use!
Now if anyone still says "this has no purpose AFAICS" or "who'd ever be needing this feature?" - OK, I won't care. But I would no longer want to live without this feature, as I'm just too lazy to retype complex lines every time. And I wouldn't want to touch the mouse for each marked line from history either, TBH. This is what xsel was written for.
BTW, the tr part of the pipe is a dirty hack which will prevent the command from being executed. For "dangerous" commands, it is extremely important to always leave the user a way to look before he/she hits the Enter key to execute it. You may omit it, but ... you have been warned.
P.S. This scriptlet is in fact a workaround, simulating !1234 typed on a bash shell. As I could never make the ! work directly in a script (echo would never let me reveal the contents of history line 1234), I worked around the problem by simply greping for the line I wanted to copy.
History expansion is part of the interactive command-line editing features of a shell, not part of the scripting language. It's not generally available in the context of a script, only when interacting with a (pseudo-)human operator. (pseudo meaning that it can be made to work with things like expect or other keystroke repeating automation tools that generally try to play act a human, not implying that any particular operator might be sub-human or anything).

bash script: How to implement your own history mechanism?

I'm implementing an interactive bash script similar to the MySQL client, /usr/bin/mysql. In this script I need to issue various types of 'commands'. I also need to provide a history mechanism whereby the user can use the up/down arrow keys to scroll through the commands entered so far.
The snippet listed here (Example 15-6, Detecting the arrow keys) does not exactly do what I want it to. I really want the following:
The up/down arrow keys should operate in silent mode. Meaning, they should not echo their character codes on the terminal.
The other keys however (which will be used to read the command names and their arguments) must not operate in silent mode.
The problem with read -s -n3 is that it does not satisfy my simultaneously conflicting requirements of silent mode and echo mode, based solely on the character code. Also, the value -n3 will work for arrow keys but, for other/regular keys, it won't 'return control' to the calling program until 3 characters have been consumed.
Now, I could try -n1 and manually assemble the input, one character at a time (yuck!). But the character-code based silent-/echo-mode switching problem would still persist!
Has anyone attempted this thing in bash? (Note: I cannot use C, nor other scripting languages like Perl, Python, etc.)
EDIT
Continuing with Dennis' answer... You will also need to manually add your desired entries to your history via history -s, like so...
while read -e x; do
history -s "$x"
# ...
done
You can use read -e to have read use readline. It will process your cursor keys and maintain the history for you. You will also need to manually add your desired entries to your history via history -s, like so:
while read -e x; do
history -s "$x"
# ...
done
MySQL and Bash use the Readline library to implement this. Maybe you could use something like rlwrap or rlfe?
rlwrap has a special "one-shot" mode to act as a replacement for the 'read' shell command. If you wish, every occurrence of this command in your script can be given its own history and completion word list.
Use it like this:
REPLY=$(rlwrap -o cat)
or, specifying a history file and a completion wordlist:
REPLY=$(rlwrap -H my_history -f my_completions -o cat)

Resources