11

I want to find a file and then enter the directory containing it. I tried find /media/storage -name "Fedora" | xargs cd but of course, I the is not a directory error.

How do I enter its parent directory with a one line command?

  • 1
    And what if there are multiple files from multiple locations ? – Sergiy Kolodyazhnyy Jan 14 '17 at 23:13
  • @Serg Im searching for Fedora*.iso file and I know there is only one. If there were more then one it would enter the first direcotry, I guess – Hrvoje T Jan 14 '17 at 23:16
  • In bash with shopt -s globstar, you could cd /media/storage/**/Fedora, but that doesn't stop evaluating the glob at the first match (so it's slower than steeldriver's solution. For interactive use, what I would normally do is reach for the mouse and copy/paste the directory name, (and alt+backspace as needed to strip off trailing path components I didn't want), but if you do this a lot I guess a shell function could be worth making. – Peter Cordes Jan 15 '17 at 3:12
  • 1
    BTW, xargs cd can't possibly work. cd can only work as a shell builtin, because it has to modify the context of the shell itself. There's no way an xargs child process can do that. IDK if that's what you meant by "of course", or if the path that find prints contains spaces, which are split by xargs since you didn't use -d \n or anything. Or find -exec {} \;. – Peter Cordes Jan 15 '17 at 3:16
  • note: you can not run cd like that. cd is a bash built in, if cd were a separate command, then it would change (its own) dir, and then quit (returning you to the shell, that is in the same state as before, no change of dir). – ctrl-alt-delor Jan 15 '17 at 14:02
14

At least if you have GNU find, you can use -printf '%h' to get the directory

       %h     Leading directories of file's name (all but the last ele‐
              ment).  If the file name contains no slashes (since it is
              in  the  current  directory)  the %h specifier expands to
              ".".

So you could probably do

cd "$(find /media/storage -name "Fedora" -printf '%h' -quit)"

The -quit should prevent multiple arguments to cd in the case more than one file matches.

| improve this answer | |
  • 1
    -quit is also not necessarily supported. In NetBSD it's called -exit, see unix.stackexchange.com/a/62883/117599 – phk Jan 14 '17 at 23:16
  • 2
    If there isn't printf could you do -exec dirname instead? – Guy Jan 14 '17 at 23:31
  • @Guy good idea yes that sounds like it should work too – steeldriver Jan 14 '17 at 23:35
6

Similar to steeldriver's solution but using -execdir (if your find supports it, like GNU's or FreeBSD's find) in combination with pwd:

cd "$(find /media/storage -name "Fedora" -execdir pwd \; -quit)"

-quit is optional in case only there is only a single result and crawling the whole directory there is of no issue. On NetBSD it's -exit and on OpenBSD it does not exist.

| improve this answer | |
  • And what is \; for? – Hrvoje T Jan 14 '17 at 23:22
  • 1
    @HrvojeT Just like for -exec it tells find about the end of parameters for the command to execute. But since we want to call pwd without parameters here, we put the \; right after it. – phk Jan 14 '17 at 23:24
  • Are there any find implementations that support execdir but not -printf %h? Seems unlikely to me. Unfortunately neither one is required by POSIX :/ – Peter Cordes Jan 15 '17 at 3:18
  • 1
    @PeterCordes FreeBSD's find: freebsd.org/cgi/man.cgi?find%281%29 (Just confirmed it on a FreeBSD 11 installation.) – phk Jan 15 '17 at 12:47
  • @PeterCordes Same for NetBSD (netbsd.gw.com/cgi-bin/man-cgi?find++NetBSD-current) and OpenBSD (man.openbsd.org/OpenBSD-current/man1/find.1). Latter does not support -quit/-exit at all though. – phk Jan 15 '17 at 14:07
5

You can make find run a new shell in the directory it finds.

exec find /media/storage -name "Fedora" -execdir "$SHELL" \;

, after which the current directory will be the one which has a file named Fedora in it. ;)

Obviously this only does something resembling what you want if you are typing commands interactively.

| improve this answer | |
4

With zsh:

cd /media/storage/**/Fedora([1]:h)

to cd into the first (in alphabetical order) directory that contains a file called Fedora.

  • **: any level of directories (hidden dirs are omitted by default, use the D glob qualifier to include them)
  • [1]: only the first
  • :h: head modifier: take the dirname.

Contrary to cd "$(find ...)", it also works if the directory name ends in a newline character. Another advantage is that you'd get a no match error message when there's no matching directory (while in most shells cd "" would do nothing silently).

A drawback is that it would crawl the whole of /media/storage before returning.

| improve this answer | |
  • In bash, cd with multiple args only looks at the first arg anyway, so cd $(dirname /media/storage/**/Fedora) would work (with shopt -s globstar) if there are no spaces in the path. To get it quoted properly, I think a bash array is easiest: target=(/media/storage/**/Fedora); cd "${target%/*}". But at that point it would have been faster to use the mouse to copy/paste find output instead of coming up with that interactively. – Peter Cordes Jan 15 '17 at 18:40
  • 2
    @PeterCordes, many dirname implementations won't accept more than one argument. Note that it's not spaces, it's any character currently in $IFS (space, tab and newline by default) and wildcard characters. Note that whether bash's cd will accept more than one argument depends on how it was compiled (CD_COMPLAINS in config-top.h). One can imagine that future versions of bash will also eventually implement the two arg feature like in zsh. – Stéphane Chazelas Jan 15 '17 at 20:51
  • Thanks. I just looked at the GNU coreutils dirname manpage. The dirname version is terrible anyway; I only mentioned it as something you might try interactively in case it worked. My array-based version doesn't suffer from any of those problems, since "${target%*/}" expands to only the first array element (with the /Fedora stripped). I think that version is fully robust against any possible characters in the pathname. – Peter Cordes Jan 15 '17 at 23:35

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.