10

When I use find, it often finds multiple results like

find -name pom.xml
./projectA/pom.xml
./projectB/pom.xml
./projectC/pom.xml

I often want to select only a specific result, (e.g edit ./projectB/pom.xml). Is there a way to enumerate find output and select a file to pass into another application? like:

find <print line nums?> -name pom.xml
1 ./projectA/pom.xml
2 ./projectB/pom.xml
3 ./projectC/pom.xml

!! | <get 2nd entry> | xargs myEditor

?

[Edit] I've bumped into some perculiar bugs with some of the solutions mentioned. So I'd like to explain steps to reproduce:

git clone http://git.eclipse.org/gitroot/platform/eclipse.platform.swt.git
cd eclipse.platform.swt.git
<now try looking for 'pom.xml' and 'feature.xml' files>

[Edit] Solution 1 So far a combination of 'nl' (enumirate output), head & tail seems to work if I combine them into functions and use $(!!).

i.e:

find -name pom.xml | nl   #look for files, enumirate output.

#I then define a function called "nls"
nls () {
  head -n $1 | tail -n 1
}

# I then type: (suppose I want to select item #2)
<my command> $(!!s 2)

# I press enter, it expands like: (suppose my command is vim)
vim $(find -name pom.xml |nls 2)

# bang, file #2 opens in vim and Bob's your uncle.

[Edit] Solution 2 Using "select" seems to work quite well as well. e.x:

  findexec () {
          # Usage: findexec <cmd> <name/pattern>
          # ex: findexec vim pom.xml
          IFS=$'\n'; 
          select file in $(find -type f -name "$2"); do
                  #$EDITOR "$file"
                  "$1" "$file"
                  break
          done;  
          unset IFS
  }

4 Answers4

16

Use bash's built-in select:

IFS=$'\n'; select file in $(find -type f -name pom.xml); do
  $EDITOR "$file"
  break
done; unset IFS

For the "bonus" question added in the comment:

declare -a manifest
IFS=$'\n'; select file in $(find -type f -name pom.xml) __QUIT__; do
  if [[ "$file" == "__QUIT__" ]]; then
     break;
  else
     manifest+=("$file")
  fi
done; unset IFS
for file in ${manifest[@]}; do
    $EDITOR "$file"
done
# This for loop can, if $EDITOR == vim, be replaced with 
# $EDITOR -p "${manifest[@]}"
DopeGhoti
  • 76,081
  • 4
    +1 for offering what I suspect is a very little used command verb – Chris Davies Jul 13 '17 at 15:40
  • I'm working in it. select doesn't seem to place nicely with altering IFS. – DopeGhoti Jul 13 '17 at 15:51
  • 3
    Ah. ( IFS=$'\n'; select file in $(find -maxdepth 2 -name '*.txt'); do echo "$file"; done; ) – Chris Davies Jul 13 '17 at 15:53
  • I should have thought of C-style strings. Well done! – DopeGhoti Jul 13 '17 at 15:53
  • 1
    I'd appreciate some insight, @roaima: https://unix.stackexchange.com/questions/378282/whitespace-safe-procedural-use-of-find-into-select-in-bash – DopeGhoti Jul 13 '17 at 16:06
  • Wow, epic stuff. Exactly what I wanted. U r uber. – Leo Ufimtsev Jul 14 '17 at 18:48
  • Edit. I found a case where it breaks down :-|. I edited my post outlining steps to reproduce with the swt git repo. If I try to search for "feature.xml", I get 2 lines that output 3 times. Two items are on the first line and the 3rd item is printed on the 2nd line. Pressing 2/3 selects the 3rd item. So the second item is inacessible. I can't figure out why, there are no whitespaces in the patch or funny characters..?
    Screenshot: http://i.imgur.com/8ZYiMLg.png
    – Leo Ufimtsev Jul 14 '17 at 19:57
  • Are you certain? select will use a multi-column layout for its 'menu', so options 1 and 2 being on the same row is normal. – DopeGhoti Jul 14 '17 at 20:14
  • I must have had low-blood caffeine levels when I was reporting the issue. I re-investigated and it does actually work as expected. Thank you for nice script. (BONUS: would be cool if there'd be a way to be able to select multiple files at once from selection. Like: findExec pom.xml .... 2 4 12 such that multiple files could be opened at once. – Leo Ufimtsev Jul 17 '17 at 16:14
  • That can be done by adding a manually injected "quit" option to select, removing the break, and using a case or if statement to break on the event of choosing the quit option. – DopeGhoti Jul 17 '17 at 16:23
3

Two little functions will help you solve this provided your filenames don't contain newlines or other non-printing characters. (It does handle filenames that contain spaces.)

findnum() { find "$@" | sed 's!^\./!!' | nl; }
wantnum() { sed -nr "$1"'{s/^\s+'"$1"'\t//p;q}'; }

Example

findnum -name pom.xml
     1  projectA/pom.xml
     2  projectB/pom.xml
     3  projectC/pom.xml

!! | wantnum 2
projectB/pom.xml
Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • Handily, the explicit filename pom.xml will never contain whitespace (: – DopeGhoti Jul 13 '17 at 15:38
  • There seems to be some bug in the above. find -name pom.xml will give me a lot of output, but findnum only gives me a single line. e.g

    ./features/org.eclipse.swt.tools.feature/pom.xml ./examples/org.eclipse.swt.examples.ole.win32/pom.xml ./examples/org.eclipse.swt.examples/pom.xml ./examples/org.eclipse.swt.examples.views/pom.xml ./examples/org.eclipse.swt.examples.launcher/pom.xml ./examples/org.eclipse.swt.examples.browser.demos/pom.xml ./local-build/local-build-parent/pom.xml

    – Leo Ufimtsev Jul 14 '17 at 18:35
  • @Leo on that same dataset how did you use findnum? – Chris Davies Jul 14 '17 at 18:53
  • Hopefully this screen shot helps explain the issue: http://i.imgur.com/hfneWJn.png You may observe that feature.xml yields 3 results. with findnum feature I get an error. with fundnum pom.xml it only prints one result where as find -name pom.xml prints 3 results. I updated my question to explain how to get dataset. (it's a simple git repo) – Leo Ufimtsev Jul 14 '17 at 20:02
  • @Leo you're not using it like shown in the example. It's a drop-in replacement for find. So where you have find -name feature.xml you then use findnum -name feature.xml. – Chris Davies Jul 14 '17 at 20:35
  • 1
    @DopeGhoti, although it could be in a directory named with whitespace d: – Wildcard Jul 18 '17 at 21:48
3

you could get the head of the total outputs and tail it with -1. this can pipe the output in any other command or editor eg.

(get me 100 lines and print me at last pipe the 100) find . | head -100 | tail -1

xxx@prod01 (/home/xxx/.ssh) $ find .
.
./authorized_keys
./id_rsa
./id_rsa.pub
./id_rsa_b_x.pub
./id_rsa_a_y.pub
./known_hosts

xxx@prod01 (/home/xxx/.ssh) $ find . | head -3
.
./authorized_key
./id_rsa

xxx@prod01 (/home/xxx/.ssh) $ find . | head -3 | tail -1
./id_rsa    



eg: vim "$(find . | head -100 | tail -1)"

will get you the 100th line of finding.

igiannak
  • 750
  • 1
    Well, the simple solution seems to be the best. Your answer combined with 'nl' and '$(!!)' seems to work quite well actually. I posted details in my question. Thanks for response. – Leo Ufimtsev Jul 14 '17 at 20:03
1

If your goal is to edit files after a search, try sag/sack.

Example:

$ sag skb_copy                                                                
sack__option is: -ag

============> running ag! <============

===> Current Profile: no_profile
===> Using flags: 
===> Searching under: /home/fklassen/git/pvc-appliance/kernel/drivers/ixgbevf
===> Searching parameters: skb_copy


/home/fklassen/git/pvc-appliance/kernel/drivers/ixgbevf/kcompat.c
[1] 195:        skb_copy_bits(skb, offset, buffer, len) < 0)

/home/fklassen/git/pvc-appliance/kernel/drivers/ixgbevf/kcompat.h
[2] 1774:   if (skb_copy_bits(skb, offset, buffer, len) < 0)
[3] 2321:#define skb_copy_to_linear_data(skb, from, len) \
[4] 2323:#define skb_copy_to_linear_data_offset(skb, offset, from, len) \

... then to edit the last search result ....

F 4

Advantage is that you can go back later edit the first search result with

F 1
fredk
  • 111