Search code examples
arraysbashparallel-processingnulfzf

allow user to complete parallel / xargs command (function) after selecting files into array; quoting nuls correctly in printf script


This is a follow-on question to this question

In that question, I could get selected files into an array and pass them to a command / function (already exported). This question differs in that I would like the user to complete the command after selecting the files.

Main Aim: I am presented with a list of filenames (FZF). I manually select some of these. FZF then puts this subset into an array. I then want to compose an unfinished command which expects the user to complete the command and press Enter.

The filenames can have spaces in them; hence the choice of Null-separated.

I'm using FZF to select the files. It produces an array containing nul-ending filenames, I think. But the first item that FZF produces is the name of a key-press. That's why the script treats the first item of FZF's output differently.

Currently I have

#!/bin/bash
readarray -d '' out < <(fd .|fzf  --print0 -e -m  --expect=ctrl-e,ctrl-l)
if [ ${#out[@]} -eq 0 ]; then return 0
fi
declare -p out
key="$out"
y=${out[@]:1}
if [ ${#y[@]} -eq 0 ]; then return 0
fi
case "$key" in
ctrl-e ) echo do something ;;
ctrl-l ) echo do something else ;;
* )
printf -v array_str '%q ' "${y[@]}"
cmd="printf '%s\0' ${array_str} | parallel -0 wc"
read -e -i "$cmd" cmd_edited; eval "$cmd_edited" ;; #not working
esac

I have gotten close: the command looks like it should, but the NUL values are not behaving. The last line doesn't work. It is intended to print the array of files on a line with null separator and still allow the user to specify a function (already exported) before hitting Enter. The parallel command would apply the function to each file in the array.

$ls
file 1
file 2
...
...
file 100

Currently, if I choose file 3 and file 2, the output of my script looks like this:

printf "%s\0" file 3 file 2 | parallel -0

to which I might for example, append wc

But then after I type wc and press Enter I get the following result:

printf "%s\0" file 3 file 2 | parallel -0 wc
wc: file030file020: No such file or directory

Edit: I have now included the line declare -p out to make clear what FZF is producing. The results as they now appear, using Charles' modification below is:

declare -a out=([0]="" [1]="file 3" [2]="file 2" [3]="file 1")
printf '%s\0' file\ 3\ file\ 2\ file\ 1  | parallel -0 wc
wc: file030file020file010: No such file or directory

So something has obviously gone wrong with the nuls.

How do I fix the code?


Solution

  • Ignoring whether fzf and parallel do what you want, the following quite certainly doesn't:

    cmd="printf \"%s\0\" ${y[@]} | parallel -0 wc"
    

    Why? Because ${y[@]} doesn't insert quoting and escaping necessary to make the contents of the y array be expressed as valid shell syntax (to refer to the data's original contents when fed back through eval).


    If you want to insert data into a string that's going to be parsed as code, it needs to be escaped first. The shell can do that for you using printf %q:

    printf -v array_str '%q ' "${y[@]}"
    cmd="printf '%s\0' ${array_str} | parallel -0 wc"
    IFS= read -r -e -i "$cmd" cmd_edited; eval "$cmd_edited"