wting
wting

Reputation: 910

How to open `find` output with vim?

I've written a function that will find a function or class definition among Python files. It generates an argument list to be used with vim. It works for the first argument / file, but fails for subsequent files since the trailing " gets added to the file name.

Despite the correct output generated, when passed to vim it does not work. However, it works when the same output is copy and pasted at the command line. The problem is that the closing " gets parsed as part of the file name when it should be the closing end of a command string:

vim +cmd1 file1 +"file2_cmd file2_cmd file2" +"file3_cmd file3_cmd file3"

I need the function to add a literal double quote (\") when adding the command, but then parse literal quote when used with vim. The odd thing is the first literal quote gets parsed but not the end literal quote.

vim +cmd file1 +" <-- this quote works, but this one doesn't --> "

Code:

function vpfd {
    local args=''

    find . -name "*.py" \
        | xargs grep -En "(def|class) ${@}[(:]" \
        | uniq \
        | while read line; do
              name=$(echo "${line}" | awk 'BEGIN { FS=":" }; { print $1 }')
              num=$(echo "${line}" | awk 'BEGIN { FS=":" }; { print $2 }')
              if [ ! "${args}" ]; then
                  args="+${num} ${name}"
              else
                  args+=" +\"tabnew +${num} ${name}\""
              fi
          done

    if [ "${args}" ]; then
        echo "vim ${args}"
        read p
        vim $(echo ${args})
    fi

Example:

$ vpfd main
vim +33 ./bar.py +"tabnew +15 ./foo.py"

If I copy and paste the above line it works just fine, however it does not work when the function tries to open vim and pass it ${args}.

Within vim:

If I copy and paste the output line then it works correctly:

$ vim +33 ./bar.py +"tabnew +15 ./foo.py"

Upvotes: 2

Views: 691

Answers (2)

glenn jackman
glenn jackman

Reputation: 246744

This is what you need:

function vpfd {
    local args=() name num

    # in bash, putting a while loop in a pipeline implies the loop
    # runs in a subshell, thus when the subshell exits you will lose
    # any variable modifications.
    # Have the while loop read from a process substitution instead.

    # The read command can store multiple values, given the appropriate
    # field separator

    while IFS=: read -r name num; do
        if (( ${#args[@]} == 0 )); then
            args=( "+$num" "$name" )
        else
            args+=( +"tabnew +$num $name")
        fi
    done < <(
        find . -name "*.py" |
        xargs grep -En "(def|class) ${@}[(:]" |
        uniq
    )

    if (( ${#args[@]} > 0 )); then
        echo "${args[*]}"
        read -p "hit enter to continue" x
        vim "${args[@]}"
    fi
}

The form "${array[@]}" (with the @ and the double quotes) will expand the array into a list of its elements.
The form "${array[*]}" will expand the array into a single string.

Upvotes: 1

FDinoff
FDinoff

Reputation: 31419

The first problem you have is that args is not updated outside of the while loop. So instead of piping in the find you should just use a redirect.

while read line; do
      name=$(echo "${line}" | awk 'BEGIN { FS=":" }; { print $1 }')
      num=$(echo "${line}" | awk 'BEGIN { FS=":" }; { print $2 }')
      if [ ! "${args}" ]; then
          args="+${num} ${name}"
      else
          args+=" +\"tabnew +${num} ${name}\""
      fi
  done < <(find . -name "*.py" | xargs grep -En "(def|class) ${@}[(:]" | uniq)

This allows the args variable to be updated outside of the while loop.

(This didn't seem to be a problem for you but I could not get your script to work without changing this)


The next problem which was having too many quotes in your string can be fixed by changing the line

args+=" +\"tabnew +${num} ${name}\""

To

args+=" +\"tabnew +${num} ${name}"

However I am not sure this is a robust solution.

Edit The more robust solution is Ignacio Vazquex-Abrams Answer except with the quotes in the proper places.


The last problem you will have is vim only accepts ten + commands. So make sure your script accounts for that.

Upvotes: 0

Related Questions