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?
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.
-quit
is also not necessarily supported. In NetBSD it's called -exit
, see unix.stackexchange.com/a/62883/117599
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.
-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.
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
find
: freebsd.org/cgi/man.cgi?find%281%29 (Just confirmed it on a FreeBSD 11 installation.)
-quit
/-exit
at all though.
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.
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.
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
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
"${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
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.
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.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.
shopt -s globstar
, you couldcd /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.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 anxargs
child process can do that. IDK if that's what you meant by "of course", or if the path thatfind
prints contains spaces, which are split by xargs since you didn't use-d \n
or anything. Orfind -exec {} \;
.