4

I am trying to make a bash inputmenu dialog handle different types, such as, files, dates, regular text. Clicking the edit button, will simply send the user to the correct dialog to retrieve the input. For regular text, I simply want to use the rename feature of inputmenu. I cannot have the user manually select rename because I only want the rename action to be used for text inputs. Having an identical looking dialog load up with the rename action automatically selected, would allow me to solve this problem.

What I'm trying to do

I've tried to achieve this by changing the file descriptor and passing in , \n and \r characters as inputs but with no luck.

#!/bin/bash

list=(aaa bbb ccc)
selected=1

function menu1()
{
    count=0
    declare -a items
    for item in "${list[@]}"; do
        items+=($((++count)) "$item")
    done
    echo -n '2 ' > tmp.txt
    exec 4<tmp.txt
    cmd=(dialog
        --input-fd 4
        --print-size
        --print-maxsize
        --extra-button --extra-label "Edit"
        --default-button extra
        --default-item "$selected"
        --inputmenu "Select action:" 22 76 20)
    exec 3>&1
    #choices=$("${cmd[@]}" "${items[@]}" <<< ' ' 2>&1 1>&3)
    choices=$("${cmd[@]}" "${items[@]}" 2>&1 1>&3)
    retVal=$?
    exec 3>&-
    readarray -t choices <<< "${choices}"
    choices="${choices[2]}"
    echo "choices=$choices"
    echo "retVal=$retVal"
    menuAction "$retVal" "${choices[0]}"
}

function menu()
{
    count=0
    declare -a items
    for item in "${list[@]}"; do
        items+=($((++count)) "$item")
    done
    cmd=(dialog
        --print-size
        --print-maxsize
        --extra-button --extra-label "Edit"
        --default-button extra
        --default-item "$selected"
        --inputmenu "Select action:" 22 76 20)
    exec 3>&1
    choices=$("${cmd[@]}" "${items[@]}" 2>&1 1>&3)
    retVal=$?
    exec 3>&-
    readarray -t choices <<< "${choices}"
    choices="${choices[2]}"
    echo "choices=$choices"
    echo "retVal=$retVal"
    menuAction "$retVal" "${choices[0]}"
}

function menuAction()
{
    retVal="$1"
    choice="$2"
    declare -a choice="${choice[0]}"
    if [[ "$retVal" -eq 3 ]]; then
        choice=(${choice[0]})
        if [[ "${choice[0]}" == "RENAMED" ]]; then
            let selected=choice[1]
            let index=choice[1]-1
            unset choice[0]
            unset choice[1]
            list[$index]="${choice[@]}"
        fi
    fi
    [[ "$retVal" -ne 1 ]] && menu
}

menu1

Edit

I've nearly got it working using expect. Unfortunately, after expect has sent input to the dialog, it returns back to the terminal:

#!/bin/bash

/usr/bin/expect <<EOD
    set timeout 30
    spawn ./dialog1 >/dev/tty
    sleep 1
    send " "
    expect eof
EOD

NB

I am fully aware that I am trying to circumvent dialogs limitations and this is usually a bad thing, as it results in poorer code that's harder to maintain and may have unnecessary dependencies. This question should be considered more of a learning exercise. Practically, it's only worthwhile in exceptional circumstances or as an optional extra. I am creating an api maker and I will be giving the client the choice of optional enhancements, such as this, that require a hackish solution.

Dan Bray
  • 7,242
  • 3
  • 52
  • 70

2 Answers2

2

I agree with other commenters that it's probably a very bad idea but here you go:

I haven't figured how to do that with expect, but it's possible with its alternative called empty (sudo apt install empty-expect in ubuntu/debian)

It is also possible to detect or ignore Ctrl+C but not both - see comments.

#!/bin/bash

#temporary files
export MYTMP=/tmp/dialog_$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM
export MYPID=${MYTMP}_pid.tmp
export MYOUT=${MYTMP}_out.tmp
export MYINP=${MYTMP}_inp.tmp
export MYRSL=${MYTMP}_rsl.tmp

#record "real" TTY just in case (this should not be necessary in most cases)
export MYTTY=`tty`

#replace command between `dialog` and `;` with your desired command or shell script
empty -f -i ${MYINP} -o ${MYOUT} /bin/bash -c 'dialog --extra-button --extra-label "Edit" --default-button extra --inputmenu "Select action:" 22 76 20 x "xx" y "yy" >${MYTTY} 2>${MYRSL} ; kill -SIGINT `cat ${MYPID}`'

#send "ENTER" key
sleep 0.1
echo -n -e '\n' >${MYINP}

# How to input keystrokes:
# \n - enter
# \e\e - ESC
# \t - tab (next button)
# \x0e - up     (or \e[A on some terminals)
# \x10 - down   (or \e[B on some terminals)

##optional: delete whatever was in the input box by pressing "DEL" a bunch of times
#echo -n -e '\e[3~\e[3~\e[3~\e[3~\e[3~\e[3~\e[3~\e[3~\e[3~\e[3~\e[3~\e[3~\e[3~\e[3~\e[3~\e[3~' >${MYINP}

## to detect Ctrl+C uncomment next line of code
#trap "export CTRL_C_PRESSED=1" INT
## ...and replace -SIGINT with -SIGTERM on line that starts with:   empty -f ...
##(you cannot both detect and ignore the signal)
## you could then test if ${CTRL_C_PRESSED} is equal to 1

#save PID to a file and redirect user input into 'dialog'
/bin/bash -c 'echo $$ >${MYPID} ; exec cat >${MYINP}'
## to ignore Ctrl+C insert:   ; trap "" INT ;   in place of semicolon (;) on previous line
## ...and replace -SIGINT with -SIGTERM on line that starts with:   empty -f ...

##If you trap/ignore Ctrl+C the script may screw up the terminal
##in that case please run    reset    command to fix, it also clears the screen


# check result in the file as you normally do
echo "----Result: `cat ${MYRSL}` "


#remove temporary files/pipes
sleep 0.1
if [ -e ${MYPID} ]; then rm ${MYPID}; fi;
if [ -e ${MYINP} ]; then rm ${MYINP}; fi;
if [ -e ${MYOUT} ]; then rm ${MYOUT}; fi;
if [ -e ${MYRSL} ]; then rm ${MYRSL}; fi;

How it works:

  • Actual command is executed in another shell but output is piped directly to the tty
  • empty intercepts input and output and attaches them to pipes
  • necessary commands are output to the 'input' pipe
  • then TTY's actual STDIN is redirected to that same pipe
  • command result is saved in a temporary file
  • when the command finishes executing it sends Ctrl+C to cat to stop the redirection
    • (otherwise cat does not realize that the pipe no longer exists so user has to press ENTER one more time)

It may be possible to avoid temporary files but that would complicate the code even more

Jack White
  • 896
  • 5
  • 7
0

Use --inputbox

var=$(dialog --output-fd 1 --inputbox test 10 10)

It'll open input menu right away, than use data in $var as you wish.

Also consider this:

list=(
    1 aaa
    2 bbb
    3 ccc
)

selected=1

first() {
    cmd=$(
        dialog \
        --output-fd 1 \
        --default-button extra    \
        --default-item "$selected" \
        --extra-button --extra-label "Edit"  \
        --inputmenu "Select action:" 22 76 20 "${list[@]}"
    )
}

first

You'll get something like this in $cmd after running first function:

$ echo $cmd
RENAMED 2 123

Parse and use this data like this for example:

sub=($cmd)
index=${sub[1]}
value=${sub[2]}

Also check how i managed dialogs in my sshto project

Ivan
  • 6,188
  • 1
  • 16
  • 23
  • This doesn't work. It does not automatically click on the rename button. – Dan Bray Feb 16 '21 at 19:32
  • I know I could use an `inputbox` but it would be a much nicer user interface if I could use the rename function of `inputmenu`. I know I'm trying to make it work in a way it's not intended to work but I'm sure it's possible with `expect`. – Dan Bray Feb 17 '21 at 18:40
  • Also, if you know of a way to get the return value of the dialog when the cancel button has been pressed or trappable key-presses, such as CTRL+C, then I will be happy to create a new question and award you a bounty. I would probably need to record all key-presses to calculate that. I guess I should probably have just accepted the limitations of `dialog` instead of trying to make it work the way I want it to. – Dan Bray Feb 17 '21 at 18:46
  • Dialog uses different exit codes for each button there was [question](https://stackoverflow.com/questions/65574138/capturing-results-of-dialog-call-embedded-in-a-bash-function/65574933#65574933) about that. And CTRL+C can be trapped with `trap` – Ivan Feb 18 '21 at 06:20
  • I know that but just knowing the exit code is not enough. I need to know which item is selected when the cancel button is clicked or CTRL+C has been pressed, and when CTRL+C has been pressed I also need to know which button is selected. – Dan Bray Feb 18 '21 at 21:14
  • I'm afraid dialog ain't support this. Looks like you need a custom menu builder. – Ivan Feb 19 '21 at 08:22
  • I'm fully aware that dialog doesn't support what I'm trying to do, however, if I can keep track of the keys that have been pressed, it's possible, and I'm close to making the inputmenu work how I would like with expect. – Dan Bray Feb 19 '21 at 09:27