junegunn / fzf

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

feature request: support escaping of printed elements #3494

Open calestyo opened 1 year ago

calestyo commented 1 year ago

Feature Request

This is a companion to #3493, with the difference that here it's about a quoting style for the (selected) output of fzf.

I would say that fzf is quite often used from shells. But there, any meta-characters or other "strange" can easily cause problems.
Further, I'd say that often, the elements are pathnames (which may contain everything except for NUL).

A typical use-case seems to be e.g.:

$ do_something_with "$(find … | fzf)"

or:

$ file="$(find … | fzf)"

The NUL-delimited versions of these would be:

$ do_something_with "$(find … -print0 | fzf --read0 --print0)"

or:

$ file="$(find … -print0 | fzf --read0 --print0)"

There are three problems here:

  1. trailing newlines are lost
  2. POSIX declares the results unspecified when NUL is on the stdout within a command substitution. So either it might be ignored, or it might terminate the string or... whatever. Not using --print0 causes an undesired newline to be added in fzf’s output, which is however anyway stripped because of (1).
  3. this doesn't work anymore, when -m multiple elements are selected, either because of (2) or, if --print0 is not used, because the separator would again be newline, which would be indistinguishable from newlines in the elements.

One workaround in bash (only) is something like:

printf_from_stdin()
{
    local stdin
    IFS='' read -r -d '' -u 0 stdin
    printf '%q' "${stdin}"
}

together with:

eval "var=$(find . -mindepth 1 -maxdepth 1 -type d -print0 | fzf --read0 --print0 | printf_from_stdin)"

after which var may hold e.g. 'k'$'\n\n'
respectively (for the fzf -m case):

i=0
find . -mindepth 1 -maxdepth 1 -type d -print0 | fzf -m --read0 --print0 | \
while true; do
    eval "var=$(printf_from_stdin)"
    if [ -z "${var}" ]; then
        break
    fi

    printf ':%s:%s::\n' "${i}" "${var}"
    i=$(($i+1))
done

(with some sugar for display purposes)

Main problems with that: a. Works only in bash (POSIX’ read doesn't have the -d and -u options) b. Using that is rather ugly ^^

So the idea of this feature request would be that fzf gets a function similar to ls--quoting-style=-option, but specifically for output, so that one could e.g. do something like.

$ ls -al $(find . -mindepth 1 -maxdepth 1 -type d -print0 | fzf -m --read0 --print0 --output-quoting-style=shell-escape)

Assuming that shell-escape is a schema that escapes in such a way that it's re-usable as input to the shell.

Of course one could have various quoting styles, e.g. for other shells that are not POSIX compatible. Also, current POSIX doesn't support $'…'-style quoting (but the next upcoming issue of POSIX will), so it might make sense to add a style for old POSIX,... or perhaps one for fish (if that's special in that regard - I don't know).

Cheers, Chris.

calestyo commented 1 year ago

Oh and just for the records:

If I'm not mistaken, e.g. bash's implementation of %q in its printf builtin can be found at: https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/builtins/printf.def#L595-L644

with the actual work horses being: https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/lib/sh/strtrans.c#L230-L317

or:

https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/lib/sh/shquote.c#L261-L312

(notice that these are unofficial git mirrors)

Maybe ls does it a bit different.

Also note, that for such functionality to be useful in the way I propose, it would be crucial that the escaping is done right, so that and arbitrary input string is quoted in a manner so that it can really be safely re-used as input (e.g. in eval) without any expansions/substitutions/etc. happening.