Can anyone point me at a 'pick' filter script that works somewhat as described below?

I've spent about an hour hunting for a simple bash script/filter that will allow me to pipe in a list of values and will spit out a subset of them depending on choices I make at the console. I know there are examples written in C but I wanted a mostly-portable bash script I can use in Cygwin / Gitbash etc. (The context: I want to be able to run some command in some subdirectories, and I want to separate the choice of which directories to run the command, from the choice of command to run.)

As hypothetical example of usage:

$ echo "foo
> bar
> baz" | pick.sh
* Options:
* 1. foo
* 2. bar
* 3. baz
* Choices? 2 3 

The lines marked * are supposed to be where the script interactively lets me choose which elements to 'pick' and once I decided lines 2 and 3 it proceeds to send those out to STDOUT.

Choices ideally could be a combination of space-separated numbers eg 2 3 4, inclusive ranges eg 2-4 .. or maybe even fancy enough to use some kind of autocompletion allowing typing the first few letters of the items themselves.

Well, there it is, I think it would be a very useful bash pipeline filter in general!

(Thanks for reading this far..)

  • The right tool: vipe. Jun 5 '20 at 22:56
  • @Kamil I'd upvote this elegant general solution if I could. Do you know if it's easy to install in GitBash?
    – Razzle
    Jun 6 '20 at 11:31
  • I have no experience with GitBash at all. Sorry, I don't know. Jun 6 '20 at 12:27
  • I just copied the vipe script from joeyh's git repo and stuck it in my bin folder.
    – Razzle
    Jun 8 '20 at 7:26

Self-demonstrating example using sed line specifiers (where N,M means lines N through M):

$ ./pick.sh < ./pick.sh
     1  #!/usr/bin/env bash
     3  set -o errexit -o nounset -o noclobber
     5  trap 'rm --force --recursive "$working_directory"' EXIT
     6  working_directory="$(mktemp --directory)"
     7  input_file="${working_directory}/input.txt"
     9  cat > "$input_file"
    11  echo 'Lines:'
    12  cat --number "$input_file"
    14  IFS=' ' read -p 'Choices: ' input < /dev/tty
    15  lines=($input)
    17  sed --quiet "$(printf '%sp;' "${lines[@]}")" "$input_file"
Choices: 1 5,7 17
#!/usr/bin/env bash
trap 'rm --force --recursive "$working_directory"' EXIT
working_directory="$(mktemp --directory)"
sed --quiet "$(printf '%sp;' "${lines[@]}")" "$input_file"

Basically, save standard input to a temporary file, print the file with line numbers, prompt for input ranges, and pass the input ranges to sed to print each of them.

One quirk of this method is that lines will be printed in the order they appear in the file, not the order you specify:

Choices: 3 1
#!/usr/bin/env bash
set -o errexit -o nounset -o noclobber

If you really need input order it would be simple to loop over lines, although this is of course less efficient.

The script assumes you have GNU cat, sed, etc. installed. If you're using BSD tools the command flags will be different.

  • Cheers @l0b0 (greetings to Absolutely Positively Wellington btw), I'm guessing use of comma as range separator is here driven by bash's array access syntax (been away from bash for about a decade..) so it should be straightfwd to also accept hyphens too.
    – Razzle
    Jun 6 '20 at 11:16
  • Nothing to do with Bash, it's sed that expects ranges to be comma separated. For example, sed --quiet 1,3p will print the first three lines of standard input. You could do a sed 's/-/,/g' to preprocess $input before creating the array to support hyphens.
    – l0b0
    Jun 6 '20 at 11:24
  • Thanks, I took a closer look, and adapted the sed args with sed 's/,/ /g;s/-/,/g;s/ p/p/g' to allow comma-separating single line numbers in the input, and hypen-separating ranges.. which feels more natural when typing in my choices. (Last clause not necessary just tidies out some spaces in sed command.)
    – Razzle
    Jun 6 '20 at 16:24
  • Discovered minor problem when pick.sh called in backticks within another script like: ` for d in `cat subdirs.txt | pick.sh` do pushd $d # process commands in selected dir e.g. mvn clean popd done` The list of choices was not seen in the terminal (presumably going into output stream) till I appended 1>/dev/tty to your echo/cat commands (lines 11-12).
    – Razzle
    Jun 6 '20 at 17:03
  • Sorry I ran out of 'edit time' on prev comment trying to get the excerpt to render correctly.. I've tried three backticks as code fences, I've tried <code> tags and <pre> tags, nothing seems to work in comments sorry!
    – Razzle
    Jun 6 '20 at 17:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.