2

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:

  • error message: Vim: not an editor command: 33 ./bar.py +"tabnew +15 ./foo.py"
  • only empty file visible
  • I exit empty file
  • vim then opens ./bar.py at correct line and on a second tab opens the incorrect file ./foo.py" (trailing ")

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

$ vim +33 ./bar.py +"tabnew +15 ./foo.py"
11
  • BASH FAQ entry #50: <a href="mywiki.wooledge.org/BashFAQ/050">"I'm trying to put a command in a variable, but the complex cases always fail!"</a> Commented May 24, 2013 at 2:14
  • In +"tabnew +15 ./foo.py" the double quotes are necessary so that vim recognizes that as a single command. Without the quotes vim interprets +tabnew +15 ./foo.py as 3 separate commands. Commented May 24, 2013 at 3:04
  • Okay, so put them there then. Commented May 24, 2013 at 3:47
  • It doesn't work when you put them there, that's the main issue. Commented May 24, 2013 at 5:17
  • Then put them wherever you need them. Commented May 24, 2013 at 5:18

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the answer, however there seems to be some confusion since all of the replies are answering the wrong question. I've updated my question with clarifying details but succinctly: args is updated properly, the correct output is generated, however correct output only works when copy pasted.
I completely understand that. The problem is that if args="+33 ./bar.py +\"tabnew +15 ./foo.py\"" then when you say vim $args, vim receives 5 parameters, not 3. That's why we're all telling you to use an array, because that technique will give the correct number of arguments to vim. You can get away with eval vim $args, but then you open yourself up to the risks of eval.
0

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.

4 Comments

args is being updated outside the while loop, and args+=" +\"tabnew +${num} ${name}" removes the necessary ending ".
@wting did you try it? Using these changes I got it to work however the reason I said it was not a robust solution is you are now relying on bash to put that ending quote in there.
@wting I can't view pastebin where I am
@wting You didn't try removing the \" from args+=" +\"tabnew +${num} ${name}\"" Also you so function has no output.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.