junegunn / fzf

:cherry_blossom: A command-line fuzzy finder
https://junegunn.github.io/fzf/
MIT License
63.17k stars 2.37k forks source link

[question] elegant way to call function in an previous instead of call script file #3599

Open marslo opened 6 months ago

marslo commented 6 months ago

Info

Problem / Steps to reproduce

The question is inspired from $ unset **<TAB>. I'd like to have a function to enhance the format and something else for environment variable.

So, theoretical basis to get value of variable value:

$ foo='SHELL'      # `SHELL` is the env var name

$ echo "${foo} - $(eval echo \$$foo)"
SHELL - /usr/local/bin/bash                  # i.e.: this is what I want to show in preview
# or
$ echo "${foo} - ${!foo}"
SHELL - /usr/local/bin/bash

I had a function ( getenv for example ) as below:

$ cat ~/ee.sh
#!/usr/bin/env bash
# shellcheck disable=SC2086

function _echo_values() { echo -e ">> $1\n.. $(eval echo \$$1)"; }
function getenv() {
  local option
  local -a array

  option='-1 --exit-0 --sort --multi --cycle'

  while read -r _env; do
    _echo_values $_env                                           # expected: show in terminal
    array+=( "${_env}=$(eval echo \$${_env})" )
  done < <( env |
            sed -r 's/^([a-zA-Z0-9_-]+)=.*$/\1/' |
            fzf ${option} \
                --prompt 'env> ' \
                --preview-window 'right,70%,wrap,rounded' \
                --preview "_echo_values {}" \                    # expected: show in preview
                --header 'TAB/SHIFT-TAB to select multiple items, CTRL-D to deselect-all, CTRL-S to select-all'
          )
  pbcopy < <( printf '%s\n' "${array[@]}" | head -c-1 )
}

# source the function and verify
$ source ~/ee.sh
$ _echo_values SHELL                                           # verify
>> SHELL
.. /usr/local/bin/bash                                         # works

however, in the fzf preview window, it shows issue:

/usr/local/bin/bash: line 1: _echo_values: command not found
getenv-1

And the bash works:

image

I've tried to move _echo_values() inside of function getenv() as below, but still no lucky:

function getenv() {
  local option
  local -a array

  option='-1 --exit-0 --sort --multi --cycle'
  _echo_values() { echo -e ">> $1\n.. $(eval echo \$$1)"; }     # moved inside of `getenv`, cannot be call in terminal, but can be identified inside the `getenv()`

  while read -r _env; do
    _echo_values $_env                                          # works here
    array+=( "${_env}=$(eval echo \$${_env})" )
  done < <( env |
            sed -r 's/^([a-zA-Z0-9_-]+)=.*$/\1/' |
            fzf ${option} \
                --prompt 'env> ' \
                --preview-window 'right,70%,wrap,rounded' \
                --preview "_echo_values {}" \                    # issue here: `/usr/local/bin/bash : line 1: _echo_values: command not found`
                --header 'TAB/SHIFT-TAB to select multiple items, CTRL-D to deselect-all, CTRL-S to select-all'
          )
  pbcopy < <( printf '%s\n' "${array[@]}" | head -c-1 )
}

So, I tried to move the _echo_values() to separate files and it works:

image

I'd like to know whether if any elegant way can handle call a function in --preview instead of call a shell script file

marslo commented 6 months ago

hmmm.... I realized that maybe I can use --nth to prevent call function/scripts to show {1} in list, and {2} in previous, and it indeed works :

$ env |
  sed -r 's/^([a-zA-Z0-9_-]+)=(.*)$/\1 \2/' |
  fzf --delimiter=' ' \
      --preview='source ~/.marslo/bin/bash-color.sh; echo -e "$(c Ys)>> {1}$(c)\n$(c Wi).. {2}$(c)"' \
      --with-nth=1

https://github.com/junegunn/fzf/assets/5822057/d7f8ec28-40e9-465c-a1e2-35a2cd2f091f

but I'm still curious how to call function instead of scripts, for more complex process to preview

marslo commented 6 months ago

suddenly realized that, I'm struggling for so long is because of " and ' are different behavior in --preview !

I was using " ( double quotes ) in the very beginning, and it's not working, so I decided to using function instead of cmd. Accidental I was using ' ( single quote ) and it worked !!

here are details:

details:

https://github.com/junegunn/fzf/assets/5822057/7249e81b-de68-4084-86c6-826b4a4edb7e

my environment:

$ sw_vers
ProductName:        macOS
ProductVersion:     14.2.1
BuildVersion:       23C71

$ bash --version
GNU bash, version 5.2.26(1)-release (x86_64-apple-darwin23.2.0)
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

$ fzf --version
0.45.0 (brew)
LangLangBart commented 6 months ago

I'd like to know whether if any elegant way can handle call a function in --preview instead of call a shell script file

Functions are not visible to child processes unless they are exported with export -f.

#!/usr/bin/env bash
foo() {
    echo foo
}
export -f foo
fzf --preview 'foo'

[!NOTE] Please note, if your login shell isn't bash (for example, on macOS the default is zsh), the function still won't work unless you change the SHELL environment variable to bash.

SHELL="$(which bash)" fzf --preview 'foo'

fzf starts child processes using $SHELL -c COMMAND


Alternatives:

  1. make it a string and call the variable
foo=$'echo foo'
: | fzf --preview "$foo"
  1. place the function in a file and source it
    foo() {
     echo foo
    }
    : | fzf --preview 'source /path/to/file/with/function; foo'
marslo commented 6 months ago

I'd like to know whether if any elegant way can handle call a function in --preview instead of call a shell script file

Functions are not visible to child processes unless they are exported with export -f.

#!/usr/bin/env bash
foo() {
  echo foo
}
export -f foo
fzf --preview 'foo'

export -f foo work like a charm !

Alternatives:

  1. make it a string and call the variable
foo=$'echo foo'
: | fzf --preview "$foo"
  1. place the function in a file and source it
foo() {
     echo foo
}
: | fzf --preview 'source /path/to/file/with/function'; foo'

for alternatives solution, how to handle fzf get from pip ( | ) ? i.e.:

$ foo() { echo ${!1}; }

# fzf from `env | sed`:
$ env | sed -rn 's/^([a-zA-Z0-9]+)=.*$/\1/p' | fzf --preview-window 'up,30%,wrap,rounded' --preview "foo {} "

I've tried:

: | env | sed ... | fzf      # < not working
env | sed ... | : | fzf      # < not working
LangLangBart commented 6 months ago

I've tried:

: | env | sed ... | fzf      # < not working
env | sed ... | : | fzf      # < not working

I'm not entirely clear on the problem. The colon :[^1] signifies nothing and can be omitted. It's only useful if you want to initiate fzf with an empty list.

foo=$'input={};echo "${!input}"'
command env | sed -rn 's/^([a-zA-Z0-9]+)=.*$/\1/p' | SHELL=bash fzf --preview "$foo"

[^1]: Bourne Shell Builtins (Bash Reference Manual)