2

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 
bar
baz

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..)

Razzle
  • 123

1 Answers1

2

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

$ ./pick.sh < ./pick.sh
Lines:
     1  #!/usr/bin/env bash
     2  
     3  set -o errexit -o nounset -o noclobber
     4  
     5  trap 'rm --force --recursive "$working_directory"' EXIT
     6  working_directory="$(mktemp --directory)"
     7  input_file="${working_directory}/input.txt"
     8  
     9  cat > "$input_file"
    10  
    11  echo 'Lines:'
    12  cat --number "$input_file"
    13  
    14  IFS=' ' read -p 'Choices: ' input < /dev/tty
    15  lines=($input)
    16  
    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)"
input_file="${working_directory}/input.txt"
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.

l0b0
  • 51,350
  • 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 06 '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 06 '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 06 '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 06 '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 tags and
     tags, nothing seems to work in comments sorry!
    – Razzle Jun 06 '20 at 17:10
  • That's a separate issue, so it should be a separate question. But it looks like you need a while read loop. – l0b0 Jun 06 '20 at 20:05