在Linux中查找文件并进入该目录

32
在一个shell脚本中,我如何根据特定文件名查找文件并导航到该目录以进行进一步操作?
从这里开始,我将把该文件复制到另一个目录(但是我已经可以做到这一点,只是为了提供上下文而添加这个问题)。
12个回答

34

你可以像这样使用:

cd -- "$(dirname "$(find / -type f -name ls | head -1)")"

这个命令会定位到第一个ls正则文件,然后转到该目录。

关于每个部分的作用:

  • find 从根目录/开始向下搜索,列出所有正则文件(-type f),名为ls(-name ls)。您可以添加其他内容来进一步限制要获取的文件。
  • | head -1 将过滤除了第一行之外的所有内容。
  • $() 是将一个命令的输出放在另一个命令的命令行上的一种方法。
  • dirname 可以接受完整的文件路径,并给您路径部分。
  • cd 只是更改为该目录,--用于防止以连字符开头的目录名被视为cd的选项。

如果按顺序执行每个部分,您就可以看到发生了什么:

pax[/home/pax]> find / -type f -name ls
/usr/bin/ls

pax[/home/pax]> find / -type f -name ls | head -1
/usr/bin/ls

pax[/home/pax]> dirname "$(find / -type f -name ls | head -1)"
/usr/bin

pax[/home/pax]> cd -- "$(dirname "$(find / -type f -name ls | head -1)")"

pax[/usr/bin]> _

1
我认为你需要在“-type f”和“-name ls”之间加一个空格。 - Dennis Williamson
@Ross:请注意,每个 $() 周围应该有双引号(否则您的脚本将无法处理包含空格和其他特殊字符的路径):cd "$(dirname "$(find / -type f-name ls | head -1)")" - Gilles 'SO- stop being evil'
它仍然会在包含换行符的目录名称上中断。 - Philipp
1
是的,如果有人在他们的文件名中使用换行符、退格符、制表符和其他有趣的字符,应该把他们打死,分成小块,然后将这些块发射到宇宙的最远处,在各种恒星的心中燃烧 :-) - paxdiablo
纯天才!感谢你解析出来。其他人指出它不是百分之百可靠的,但我真的很喜欢这个简单的解决方案。它正是我99.9%需要的,而且很容易将其包装在.bashrc函数中以便快速导航。这让我的一天都变得美好了! - Matthew Kraus
显示剩余3条评论

14
以下做法更安全:
cd -- "$(find / -name ls -type f -printf '%h' -quit)"

优点:

  • 双破折号可以防止以连字符开头的目录名被解释为选项(find不会生成这样的文件名,但它并非有害且可能需要类似的构造)
  • -name 检查在 -type 检查之前,因为后者有时需要一个 stat
  • 无需 dirname,因为 %h 指示符已经打印出目录名称
  • -quit 可以在找到第一个文件后停止搜索,因此不需要 head,从而避免脚本在包含换行符的目录名上失败

1
对我来说,这是最好的答案。大多数情况下可以缩短并遵循以下格式:1)使用find命令查找所需内容,例如:`$ find . -name *.xsd`2)添加printf和-quit参数`$ find . -name *.xsd -printf "%h" -quit`3)用反引号括起来并输入cd(如果不需要则忽略--):`$ cd \`find . -name *.xsd -printf "%h" -quit\`` - Jan Vlcinsky
-printf '%h' 这部分使得该命令实际上不返回任何内容,即使有文件存在...不太确定为什么。 - phil294
@JanVlcinsky 使用反引号而不是引号对我不起作用。它失败并显示“path/to/folder: 是一个目录”。 - mbomb007

5
没有人建议使用locate(对于大型树来说更快),是吗?
zsh:
cd $(locate zoo.txt|head -1)(:h)
cd ${$(locate zoo.txt)[1]:h}
cd ${$(locate -r "/zoo.txt$")[1]:h}   

or could be slow

cd **/zoo.txt(:h)

bash:

cd $(dirname $(locate -l1 -r "/zoo.txt$"))

4

基于这个答案给出的类似问题的解决方案,另一个有用的选择是使用两个命令,第一个用于查找文件,第二个用于进入其目录:

find ./ -name "champions.txt"
cd "$(dirname "$(!!)")"

在IT技术中,“!!”是历史扩展的意思,表示“上一个命令”。


4

进一步补充之前的回答,如果您想迭代地浏览每个find定位到的文件,并在每个目录中执行操作:

for i in $(find /path/to/search/root -name filename -type f)
do (
  cd $(dirname $(realpath $i));
  your_commands;
)
done

Shellcheck建议不要使用find循环,因为它们容易出错:https://github.com/koalaman/shellcheck/wiki/SC2044 - MakisH

1
function fReturnFilepathOfContainingDirectory {
    #fReturnFilepathOfContainingDirectory_2012.0709.18:19
    #$1=File

    local vlFl
    local vlGwkdvlFl
    local vlItrtn
    local vlPrdct

    vlFl=$1
    vlGwkdvlFl=`echo $vlFl | gawk -F/ '{ $NF="" ; print $0 }'`
    for vlItrtn in `echo $vlGwkdvlFl` ;do
        vlPrdct=`echo $vlPrdct'/'$vlItrtn`
    done
    echo $vlPrdct

}

1
这样简单,不是很优雅吗?
cdf yourfile.py

当然,您需要先进行设置,但这只需执行一次

将以下行添加到您的.bashrc或.zshrc中,无论您使用哪个作为您的shell初始化脚本。

source ~/bin/cdf.sh 

将以下代码添加到需要从头创建的~/bin/cdf.sh文件中。

#!/bin/bash

function cdf() {
    THEFILE=$1
    echo "cd into directory of ${THEFILE}"
    # For Mac, replace find with mdfind to get it a lot faster. And it does not need args ". -name" part.
    THEDIR=$(find . -name ${THEFILE} |head -1 |grep -Eo "/[ /._A-Za-z0-9\-]+/")
    cd ${THEDIR}
}

1
如果你只是要找到文件并将其移动到其他地方,只需使用find和-exec。
find /path -type f -iname "mytext.txt" -exec mv "{}" /destination +;

2
我刚遇到了一个类似的情况,我想要重命名不同目录下同名的文件副本。find-execdir选项对我很有帮助:find . -name foo.txt -execdir mv "{}" bar.txt ";"可以在每个发现它的目录中将foo.txt重命名为bar.txt。 - Andrew Hershberger

0
如果它是你的PATH中的一个程序,你可以这样做:
cd "$(dirname "$(which ls)")"

或者在Bash中:

cd "$(dirname "$(type -P ls)")"

这个使用一个较少的外部可执行文件。

这个不使用任何外部文件:

dest=$(type -P ls); cd "${dest%/*}"

在哪个 shell 中 cd 命令不需要引号?在执行 export d="foo bar"; mkdir "$d" 后,ksh -c 'cd $d' 报错 cd: bad substitutionash -c 'cd $d' 报错 can't cd to foobash -c 'cd $d' 报错 cd: foo: Not a directory - Gilles 'SO- stop being evil'
@Gilles:我敢肯定在Bash中这个是可行的。我会编辑我的回答。 - Dennis Williamson
它无法在符合POSIX标准的shell中工作,cd没有特殊规定来遵循通常的扩展规则。而且你仍然缺少对dirname参数周围的引号。 - Gilles 'SO- stop being evil'

0

如果您的文件只在一个位置,您可以尝试以下操作:

cd "$(find ~/ -name [filename] -exec dirname {} \;)" && ...

您可以使用-exec来调用dirname并传递find返回的路径(该路径将替换{}占位符)。这将更改目录。您还可以添加双个“&&”(&&)以在shell更改目录后执行下一个命令。

例如:
cd "$(find ~/ -name need_to_find_this.rb -exec dirname {} \;)" && ruby need_to_find_this.rb

它将查找该ruby文件,切换到该目录,然后从该文件夹内运行它。此示例假设文件名是唯一的,并且由于某种原因,ruby脚本必须从其目录中运行。如果文件名不唯一,则会将多个位置传递给cd,它将返回错误,然后不会更改目录


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接