Send zsh prompt to program and replace content with result - shell

I would like to send my prompt contents to my snippets application and replace the line with the result:
Example initial prompt:
$ while foo bar
Example replaced prompt:
$ while foo ; do bar ; done
Having the first prompt I would run a shortcut and replace the line with the value returned by the program.
I imagine the solution would be something like this:
bindkey "^y" evaluateSnippets
evaluateSnippets() {
return mySnippetsTool <<< "$promptLine"
}
How can I accomplish that in zsh?
A further correlated question is if it is possible to replace just the selected part of the prompt in another shortcut.

evaluate-snippets () {
BUFFER=$(mySnippetsTool <<< $BUFFER)
}
zle -N evaluate-snippets
bindkey '^Y' evaluate-snippets
Within widgets (aka the functions behind the key bindings) the content of the edit buffer is contained in the parameter BUFFER. The edit buffer can also be modified by writing to BUFFER. So saving the output of mySnippetsTool in BUFFER should be enough. The command zle -N foo creates a widget named foo, which runs the function fo the same name when called.
As you can manipulate the contents of BUFFER in any way you want, it is also possible to modify only parts of it. The main caveat here is that the selection has to be done from withing the shell - e.g. visual-mode (v) with the vicmd keybindings or set-mark-command (Control+#) with the emacs keybindings - and (probably) cannot be done with the mouse. For example:
evaluate-snippets-selection () {
if [[ $CURSOR -gt $MARK ]]; then
start=$MARK
end=$(( CURSOR + 1 ))
else
start=$(( CURSOR + 1 ))
end=$MARK
fi
BUFFER="$BUFFER[0,start]$(mySnippetsTool <<< $BUFFER[start+1,end])$BUFFER[end+1,-1]"
}
zle -N evaluate-snippets-selection
bindkey '^Z' evaluate-snippets-selection
(Note: some fine-tuning for the indices and ranges might be necessary in order to match the expectation of what is currently selected. For example whether the current cursor position is part of the selection or not.)
You might not even need separate commands. As long as you did not set a mark and the cursor is at the very end of the line, both commands should deliver the same results.

Related

Select command separates the options with spaces listed from a file [duplicate]

This question already has answers here:
Creating an array from a text file in Bash
(7 answers)
How can I write a Bash Script to display a menu, accept user input and display data?
(3 answers)
Closed 4 months ago.
How can I display a selection menu for a user with options I have stored on individual lines of a text file?
For example, my text file (ingestion.txt) looks like this.
SOUP
FTS/CTS
JDBC
NEW
And I want the user to see this
Please select an option by typing in the corresponding number
1) SOUP
2) FTS/CTS
3) JDBC
4) NEW
And then if they didn't type a valid number they would be prompted again.
#!/bin/bash
unset option menu ERROR # prevent inheriting values from the shell
declare -a menu # create an array called $menu
menu[0]="" # set and ignore index zero so we can count from 1
# read menu file line-by-line, save as $line
while IFS= read -r line; do
menu[${#menu[#]}]="$line" # push $line onto $menu[]
done < ingestion.txt
# function to show the menu
menu() {
echo "Please select an option by typing in the corresponding number"
echo ""
for (( i=1; i<${#menu[#]}; i++ )); do
echo "$i) ${menu[$i]}"
done
echo ""
}
# initial menu
menu
read option
# loop until given a number with an associated menu item
while ! [ "$option" -gt 0 ] 2>/dev/null || [ -z "${menu[$option]}" ]; do
echo "No such option '$option'" >&2 # output this to standard error
menu
read option
done
echo "You said '$option' which is '${menu[$option]}'"
This reads through ingestion.txt line by line, then pushes the contents of that line into the $menu array. ${#variable} gives you the length of $variable. When given the entirety of an array ("${array[#]}" is akin to "$#"), such as ${#array[#]}, you get the number of elements in that array. Because bash is zero-indexed, that number is the first available item you can add to, starting with zero given the newly defined (and therefore empty) array.
The menu() function iterates through the menu items and displays them on the screen.
We then loop until we get a valid input. Since bash will interpret a non-number as zero, we first determine if it is actually a (natural) number, discarding any error that would come from non-numbers, then we actually ensure the index exists. If it's not the first iteration, we complain about the invalid input given before. Display the menu, then read user input into $option.
Upon a valid option index, the loop stops and the code then gives you back the index and its corresponding value.

Is it possible to combine bash variable search and replace with substring?

I have this function:
#! /usr/bin/env bash
function underline() {
U="${1//?/${2:--}}"
echo -e "\n$1\n${U:0:${#1}}\n"
}
underline "$1" "^-v-"
will work as expected:
$ ./u.sh "This is all you're going to see"
This is all you're going to see
^-v-^-v-^-v-^-v-^-v-^-v-^-v-^-v
It does what you expect. Originally it assumed that the underline character was just that, 1 character.
For "fun", I extended it to move from a single character underline to a repeating string underline (why? ... well ... because I can I suppose ... almost zero practical value in this exercise!).
And so, being the "let's make this so difficult that you need to write 2 pages of documentation to explain what's going on" sort of guy, I was wondering if the function could be written as a single line. And I don't mean:
function underline() { U="${1//?/${2:--}}"; echo -e "\n$1\n${U:0:${#1}}\n"; }
I don't think you can combine bash's variable search/replace with substring which is what is required.
I'm aware that this will work happily in zsh:
#! /usr/bin/env zsh
function underline() {
echo -e "\n$1\n${${1//?/${2:--}}:0:${#1}}\n"
}
underline $1 "^-v-"
e.g.
$ ./u.sh "This is all you're going to see"
This is all you're going to see
^-v-^-v-^-v-^-v-^-v-^-v-^-v-^-v
but not capable of this in bash (it seems).
No, you cannot combine those in a single parameter expansion in bash. But this single printf (a builtin in bash) should do the trick:
underline() { printf '%s\n%.*s\n' "$1" ${#1} "${1//?/${2:--}}"; }
underline "This is all you're going to see" "^-v-"
outputs
This is all you're going to see
^-v-^-v-^-v-^-v-^-v-^-v-^-v-^-v

Progress Bar While Looping Script

I would like to show a progress bar while running the following code. I'm calling a script within this code that could take a while depending on the variables passed in. I've tried some of the implementations from How to add a progress bar to a shell script? but have not been able to get them to work. In theory it should continue based off of whether or not the process is still running. If it's still running then show a progress bar.
cat $BLKUPLD | tr -d '\r' | while read line;
do
device_name=`echo $line | cut -d "," -f 1`
quantity_num=`echo $line | cut -d "," -f 2`
bash $SCRIPT $device_name $quantity_num
done
Please let me know if you need additional information.
Below is a progress bar script that will run until the bar is filled.
You will want to change the condition of the outermost while loop to instead check whether your $BLKUPLD has completed, and move the rest of your code inside the while loop where indicated (essentially, you may need to change MAX_ITERATIONS to a boolean condition, and play with where to insert the components of your code within this scaffold).
Ideally you would know how much remaining data you had, and could dynamically set MAX_ITERATIONS accordingly as you enter the loop logic; however, you mentioned that you were okay with having an infinitely looping progress bar as well, which might be the way you have to go if you aren't able to pre-define the end point of your script.
The main premise behind this script that differs from the other thread I linked, is that there are no hardcoded progress points: e.g. "[###__________]33%". Instead, the nested while loops serve to dynamically set the number of hashtags, and also dynamically pad the spacing following the hashtags to maintain a consistent span of progress.
#!/bin/sh
MAX_ITERATIONS=10
WHILE_ITERATOR=0
while [ $WHILE_ITERATOR -le $MAX_ITERATIONS ]
# __Add call to process checking script here.__
do
# Appending hashtags to progress bar string.
PROGRESS_BAR="["
HASHTAGS=$WHILE_ITERATOR
HASHTAGS_ITERATOR=0
while [ $HASHTAGS_ITERATOR -le $HASHTAGS ]
do
# Accounting for first pass through outer loop.
if [ $WHILE_ITERATOR -eq 0 ]; then
PROGRESS_BAR+=" #"
else
PROGRESS_BAR+="#"
fi
HASHTAGS_ITERATOR=$((HASHTAGS_ITERATOR+1))
done
# Appending trailing spaces to progress bar string.
SPACES=$((MAX_ITERATIONS-WHILE_ITERATOR-1))
SPACES_ITERATOR=0
while [ $SPACES_ITERATOR -le $SPACES ]
do
PROGRESS_BAR+=" "
SPACES_ITERATOR=$((SPACES_ITERATOR+1))
done
# Closing progress bar screen and adding return esc char.
PROGRESS_BAR+="]\r"
# Setting echo -n to run properly on Unix & Mac
if [ "`echo -n`" = "-n" ]; then
n=""
c="\c"
else
n="-n"
c=""
fi
# Print the progress bar without \n; reprints in place.
echo $n "$PROGRESS_BAR" $c
sleep 1
WHILE_ITERATOR=$((WHILE_ITERATOR+1))
done
# Print final iteration of progress bar string.
echo "$PROGRESS_BAR"

Bash: SPACE-triggered completion

In bash, I would like to achieve the following workflow (prompt shown in brackets, _ is the cursor):
Type "foo" ($ foo_)
Press space. At this point:
if foo is a function, say function foo() { printf "hello from foo" }
space is printed ($ foo _)
the function is called and hello from foo is printed after a space - no newline ($ foo hello from foo_)
if foo is not a function, space is printed and that's it ($ foo _)
I have tried:
Making space send 0x0a (return) to the terminal emulator (iTerm2 on Mac). This works except it obviously prints the newline character as well
Using the built-in complete function: complete -F foo -o nospace foo. This works but I have to type foo then SPACE then TAB for hello from foo to be printed inline
I have heard you could somehow embed a \n-eating script into PS1. Really don't know how to get started on that one.
I could also trap a shortcut, such as Ctrl+T, to execute foo - but I'd really like to only press SPACE.
Ideally space would behave like this only for the first word being typed into the terminal. Any help would be appreciated, please save my sanity.
Why in the world I need this: (I'm a geek AND) I've got an emacs-like ido script that I'd like to be invoked when I type cd followed by SPACE.
A way to do this is using -n 1 with read. For example:
foo(){
echo hello from foo
}
string=''
while read -n 1 a ; do
if [ "$a" = "" ] ; then
tpe=`type -t $string`
if [ "$tpe" = "function" ] ; then
$string
fi
string=''
else
string="$string$a"
fi
done

Capturing multiple line output into a Bash variable

I've got a script 'myscript' that outputs the following:
abc
def
ghi
in another script, I call:
declare RESULT=$(./myscript)
and $RESULT gets the value
abc def ghi
Is there a way to store the result either with the newlines, or with '\n' character so I can output it with 'echo -e'?
Actually, RESULT contains what you want — to demonstrate:
echo "$RESULT"
What you show is what you get from:
echo $RESULT
As noted in the comments, the difference is that (1) the double-quoted version of the variable (echo "$RESULT") preserves internal spacing of the value exactly as it is represented in the variable — newlines, tabs, multiple blanks and all — whereas (2) the unquoted version (echo $RESULT) replaces each sequence of one or more blanks, tabs and newlines with a single space. Thus (1) preserves the shape of the input variable, whereas (2) creates a potentially very long single line of output with 'words' separated by single spaces (where a 'word' is a sequence of non-whitespace characters; there needn't be any alphanumerics in any of the words).
Another pitfall with this is that command substitution — $() — strips trailing newlines. Probably not always important, but if you really want to preserve exactly what was output, you'll have to use another line and some quoting:
RESULTX="$(./myscript; echo x)"
RESULT="${RESULTX%x}"
This is especially important if you want to handle all possible filenames (to avoid undefined behavior like operating on the wrong file).
In case that you're interested in specific lines, use a result-array:
declare RESULT=($(./myscript)) # (..) = array
echo "First line: ${RESULT[0]}"
echo "Second line: ${RESULT[1]}"
echo "N-th line: ${RESULT[N]}"
In addition to the answer given by #l0b0 I just had the situation where I needed to both keep any trailing newlines output by the script and check the script's return code.
And the problem with l0b0's answer is that the 'echo x' was resetting $? back to zero... so I managed to come up with this very cunning solution:
RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"
Parsing multiple output
Introduction
So your myscript output 3 lines, could look like:
myscript() { echo $'abc\ndef\nghi'; }
or
myscript() { local i; for i in abc def ghi ;do echo $i; done ;}
Ok this is a function, not a script (no need of path ./), but output is same
myscript
abc
def
ghi
Considering result code
To check for result code, test function will become:
myscript() { local i;for i in abc def ghi ;do echo $i;done;return $((RANDOM%128));}
1. Storing multiple output in one single variable, showing newlines
Your operation is correct:
RESULT=$(myscript)
About result code, you could add:
RCODE=$?
even in same line:
RESULT=$(myscript) RCODE=$?
Then
echo $RESULT $RCODE
abc def ghi 66
echo "$RESULT"
abc
def
ghi
echo ${RESULT#Q}
$'abc\ndef\nghi'
printf '%q\n' "$RESULT"
$'abc\ndef\nghi'
but for showing variable definition, use declare -p:
declare -p RESULT RCODE
declare -- RESULT="abc
def
ghi"
declare -- RCODE="66"
2. Parsing multiple output in array, using mapfile
Storing answer into myvar variable:
mapfile -t myvar < <(myscript)
echo ${myvar[2]}
ghi
Showing $myvar:
declare -p myvar
declare -a myvar=([0]="abc" [1]="def" [2]="ghi")
Considering result code
In case you have to check for result code, you could:
RESULT=$(myscript) RCODE=$?
mapfile -t myvar <<<"$RESULT"
declare -p myvar RCODE
declare -a myvar=([0]="abc" [1]="def" [2]="ghi")
declare -- RCODE="40"
3. Parsing multiple output by consecutives read in command group
{ read firstline; read secondline; read thirdline;} < <(myscript)
echo $secondline
def
Showing variables:
declare -p firstline secondline thirdline
declare -- firstline="abc"
declare -- secondline="def"
declare -- thirdline="ghi"
I often use:
{ read foo;read foo total use free foo ;} < <(df -k /)
Then
declare -p use free total
declare -- use="843476"
declare -- free="582128"
declare -- total="1515376"
Considering result code
Same prepended step:
RESULT=$(myscript) RCODE=$?
{ read firstline; read secondline; read thirdline;} <<<"$RESULT"
declare -p firstline secondline thirdline RCODE
declare -- firstline="abc"
declare -- secondline="def"
declare -- thirdline="ghi"
declare -- RCODE="50"
After trying most of the solutions here, the easiest thing I found was the obvious - using a temp file. I'm not sure what you want to do with your multiple line output, but you can then deal with it line by line using read. About the only thing you can't really do is easily stick it all in the same variable, but for most practical purposes this is way easier to deal with.
./myscript.sh > /tmp/foo
while read line ; do
echo 'whatever you want to do with $line'
done < /tmp/foo
Quick hack to make it do the requested action:
result=""
./myscript.sh > /tmp/foo
while read line ; do
result="$result$line\n"
done < /tmp/foo
echo -e $result
Note this adds an extra line. If you work on it you can code around it, I'm just too lazy.
EDIT: While this case works perfectly well, people reading this should be aware that you can easily squash your stdin inside the while loop, thus giving you a script that will run one line, clear stdin, and exit. Like ssh will do that I think? I just saw it recently, other code examples here: https://unix.stackexchange.com/questions/24260/reading-lines-from-a-file-with-bash-for-vs-while
One more time! This time with a different filehandle (stdin, stdout, stderr are 0-2, so we can use &3 or higher in bash).
result=""
./test>/tmp/foo
while read line <&3; do
result="$result$line\n"
done 3</tmp/foo
echo -e $result
you can also use mktemp, but this is just a quick code example. Usage for mktemp looks like:
filenamevar=`mktemp /tmp/tempXXXXXX`
./test > $filenamevar
Then use $filenamevar like you would the actual name of a file. Probably doesn't need to be explained here but someone complained in the comments.
How about this, it will read each line to a variable and that can be used subsequently !
say myscript output is redirected to a file called myscript_output
awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'

Resources