2

I was looking for a file in a directory and so I used the find command which then showed the directory path of where the file is.

ex.

$find . -name file.txt
/folder/path/file-contents/file.txt

Now I was wondering is there a way to cd into this directory a quicker way than copying the path directory and then using the cd command and pasting the directory?

Also if there are two paths that get listed after using the find command, is there a way to just to cd to the second path given without copying and pasting that path as well?

Just want to know to help me speed up working in terminal a little bit.

3

(bash variation further down)

In the zsh shell, the last match of a globbing pattern can be had by adding the globbing qualifier ([-1]) to the end of the pattern. In this example, we look for files whose names end in .sh:

$ print -rC1 ./**/*.sh
./local/sbin/scheduled-backup.sh
./local/sbin/update-cvs.sh
./local/sbin/update-system.sh
./local/sbin/zerofill.sh
$ print -rC1 ./**/*.sh(.D[-1])
./local/sbin/zerofill.sh

The added . and D in the qualifier makes sure that we only find regular files and that we also match hidden names (as with dotglob set in bash).

This could be used to change into the directory containing the found file:

$ cd ./**/*.sh(.D[-1]:h)

(here using the csh/vi-style :h modifier to get the head (dirname) of the file)

As a shell function that takes the name of a file (not a pattern):

goto_file () cd ./**/$1(.D[-1]:h)

This would not rely on filenames being sane (not containing newlines etc.)


In bash, assuming you first set the globstar and dotglob shell options using shopt -s globstar dotglob, you could do a similar thing in two steps:

$ set -- ./**/*.sh
$ cd "$( dirname -- "${@: -1}" )"

This sets the positional parameters to the list of matching pathnames, then uses the last of these in a call to dirname, and uses the result of that with cd. With bash, there's no guarantee that the expansion of the glob will be a regular file though.

You could obviously use a named array instead:

$ stuff=( ./**/*.sh )
$ cd "$( dirname -- "${stuff[-1]}" )"

As a shell function, taking the name of a file (not a pattern):

goto_file () {
    local pathnames

    pathnames=( ./**/"$1" )
    cd "$( dirname -- "${pathnames[-1]}" )"
}

This requires that at least the globstar option has been set in the calling shell. We can set it on demand though:

goto_file () {
    local pathnames

    if [[ $BASHOPTS != *globstar* ]]; then
        shopt -s globstar
        trap 'shopt -u globstar' RETURN   # unset globstar on return
    fi

    pathnames=( ./**/"$1" )
    cd "$( dirname -- "${pathnames[-1]}" )"
}

This, like the zsh variation, would not rely on filenames being sane (not containing newlines etc.), with the exception that the directory name cannot end in newline characters since these would be stripped off when the dirname command substitution is returning its value.

|improve this answer|||||
2

You could use dirname to get the directory name that holds the file you're searching for, and you could use tail -1 to get the last entry. Technically you could use head -2 | tail -1 to get the second listing anytime there are two or more. If there's only 1 listing, it will return that 1.

Like:

cd $(dirname $(find . -name file.txt | tail -1))

Or:

cd $(dirname $(find . -name file.txt | head -2 | tail -1))

I'm not sure if that's really faster, but it does alleviate having to use a mouse to select text, and copy it.

You could build it into a function in your shell rc, such as .bashrc.

function goto-file() {
    file=$1
    cd $(dirname $(find . -name $file | tail -1))
}

Then, your CLI efficiency would be to simply type goto-file file.txt.

Maybe better would be to allow you to pass the path you want to search? Here's the function I just added to my .bashrc:

function goto-file() {
    if [ $# -gt 2 -o $# -lt 1 ]; then
        echo "Usage: goto-file [path] filename"
    else
        if [ $# -eq 2 ]; then
            path=$1
            file=$2
        elif [ $# -eq 1 ]; then
            path='.'
            file=$1
        fi

        cd $(dirname $(find $path -name $file | tail -1))
    fi
}

Quite handy. Thanks.

|improve this answer|||||
  • 2
    If you have GNU find, find $path -printf %h allows you to skip dirname. – Stephen Kitt Dec 4 '19 at 16:35
0

If you're using the xterm terminal emulator, you could bind the dabbrev-expand() action (which mimics emacs' dynamic abbreviation feature) to some key.

For instance, put in a resource file (either loaded into the X server with xrdb so that the xterm of any client to that X server be affected, or in a XENVIRONMENT file so all xterm invocation from the machine, regardless of which X server they connect to be affected), something like:

XTerm.VT100.translations:             #override\
        Meta <KeyPress> /: dabbrev-expand()

Then with Alt+/, xterm provides completion based on what's displayed on the screen (starting with what's closest to the cursor).

So, after your find ... displaying /folder/path/file-contents/file.txt, it should be enough to type cd /Alt+/ for xterm to expand that / to /folder/path/file-contents/file.txt and then delete the trailing part (or with zsh, append (:h), or use Ctrl+W if configured to consider / as a word separator, for instance with select-word-style).

That approach won't work if the path contains whitespace or control characters or characters in decomposed form though.

And if it contains characters that are special to the shell (like ', ", ;, &...), you'd need to quote them (in zsh, you can use the quote-region widget (bound to Alt+" by default to do that) for that).

|improve this answer|||||

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.