12

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?

5
  • 1
    And what if there are multiple files from multiple locations ? Commented Jan 14, 2017 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
    Commented Jan 14, 2017 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. Commented Jan 15, 2017 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 {} \;. Commented Jan 15, 2017 at 3:16
  • @richard Thanks. Im a noob. Learning what built-in vs not built-in means. And english is not my language. So, continuing to learn hard with the help of good people
    – Hrvoje T
    Commented Jan 15, 2017 at 14:07

5 Answers 5

16

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.

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

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.

5
  • And what is \; for?
    – Hrvoje T
    Commented Jan 14, 2017 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
    Commented Jan 14, 2017 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 :/ Commented Jan 15, 2017 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
    Commented Jan 15, 2017 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
    Commented Jan 15, 2017 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.

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.

3
  • 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. Commented Jan 15, 2017 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. Commented Jan 15, 2017 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. Commented Jan 15, 2017 at 23:35
0

There as already many answers showing how to work around the problem, and how to get something that works. However none say why it did not work in the first place.

Here is why running xargs cd, can not work.

It is the same problem as writing a script to change directory.

  • xargs starts a new process / the script runs in a new process.
  • cd changes the current working directory of this new process.
  • The new process exits.

Thus the present working directory of a process that no longer exists has been changed.

Then your call cd in the shell, it uses a built in command. It does not create a new process. So the current working directory of the shell process is changed.


See other answers for what to do.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .