在父目录而不是子目录中查找搜索


45

我嵌套在文件树的深处,我想找到哪个父目录包含一个文件。

例如,我在一组嵌套的Git存储库中,想要找到控制我当前所在文件的.git目录。我希望能找到诸如-searchup -iname“ .git”之类的东西

Answers:


16

允许使用find选项的更通用的版本:

#!/bin/bash
set -e
path="$1"
shift 1
while [[ $path != / ]];
do
    find "$path" -maxdepth 1 -mindepth 1 "$@"
    # Note: if you want to ignore symlinks, use "$(realpath -s "$path"/..)"
    path="$(readlink -f "$path"/..)"
done

例如(假设脚本另存为find_up.sh

find_up.sh some_dir -iname "foo*bar" -execdir pwd \;

...将打印所有some_dir祖先的名称(包括其祖先),直到/找到带有模式的文件为止。

readlink -f如注释中所述,在使用上述脚本时,将遵循符号链接。您可以使用realpath -s替代,如果你想通过名字来遵循的路径向上(“/富/栏”将上升到“富”,即使“酒吧”是一个符号链接) -但是,需要安装realpath哪些默认是不安装大多数平台。


这些问题是它解析链接的方式。例如,如果我在〜/ foo / bar中,而bar是到/ some / other / place的符号链接,那么〜/ foo和〜/将永远不会被搜索到。
Erik Aronesty

@ErikAronesty,您是对的-更新了答案。您可以realpath -s用来获取所需的行为。
sinelaw

当您指定完整的搜索路径时,该方法将起作用。可悲的是,在centos 5.8上,realpath -s <dir>有一个bug,如果<dir>是相对的,则无论如何它都会解析符号链接...尽管有文档说明。
Erik Aronesty

readlink不是该标准的一部分。只能使用POSIX shell功能来实现可移植脚本。
schily

1
仅供参考,这在zsh中不起作用,因为它重新定义了$ path,因此shell找不到findor readlink。我建议将其更改为类似的名称,$curpath以免影响shell变量。
Skyler

28
git rev-parse --show-toplevel

如果您位于其中,它将打印出当前存储库的顶层目录。

其他相关选项:

# `pwd` is inside a git-controlled repository
git rev-parse --is-inside-work-tree
# `pwd` is inside the .git directory
git rev-parse --is-inside-git-dir

# path to the .git directory (may be relative or absolute)
git rev-parse --git-dir

# inverses of each other:
# `pwd` relative to root of repository
git rev-parse --show-prefix
# root of repository relative to `pwd`
git rev-parse --show-cdup

4
这仅适用于git本身。这个问题更为笼统。
Alex

1
这在裸
仓库中是行不通的

19

吉尔斯答案的广义版本,用于查找匹配项的第一个参数:

find-up () {
  path=$(pwd)
  while [[ "$path" != "" && ! -e "$path/$1" ]]; do
    path=${path%/*}
  done
  echo "$path"
}

继续使用符号链接。


15

查找无法做到。我想不到有什么比Shell循环更简单的了。(未经测试,假设没有/.git

git_root=$(pwd -P 2>/dev/null || command pwd)
while [ ! -e "$git_root/.git" ]; do
  git_root=${git_root%/*}
  if [ "$git_root" = "" ]; then break; fi
done

对于git存储库的特定情况,您可以让git为您完成工作。

git_root=$(GIT_EDITOR=echo git config -e)
git_root=${git_root%/*}

1
太酷了,这为我提供了一个通用的解决方案-我将其发布为此处的另一个答案。
文森特·沙伊布

12

如果使用启用了扩展的globing的zsh,则可以使用oneliner来实现:

(../)#.git(:h)   # relative path to containing directory, eg. '../../..', '.'
(../)#.git(:a)   # absolute path to actual file, eg. '/home/you/src/prj1/.git'
(../)#.git(:a:h) # absolute path to containing directory, eg. '/home/you/src/prj1'

说明(引自man zshexpn):

递归球

形式(foo/)#的路径名组件与由零个或多个与模式foo匹配的目录组成的路径匹配。作为简写,**/等效于(*/)#

修饰符

在可选的单词指示符之后,您可以添加一个或多个以下修饰符的序列,每个修饰符前均带有':'。除非另有说明,否则这些修饰符还可以处理文件名生成和参数扩展的结果。

  • 一种
    • 将文件名转换为绝对路径:如有必要,在当前目录前加前缀,并解决对“ ..”和“。”的任何使用
  • 一种
    • 作为“ a ”,还尽可能解决使用符号链接的问题。请注意,“ ..”的解析发生在符号链接的解析之前。除非您的系统具有realpath系统调用(现代系统具有),否则此调用等效于a 。
  • H
    • 删除尾随的路径名组件,保留头部。类似于“ dirname”。

学分:Faux#zsh为使用的最初建议(../)#.git(:h)


这真是太神奇了……如果我只能弄清楚(提示:您应该告诉我(../)正在做什么……哦,谁是Faux on #zsh什么?
亚历克斯·格雷

1
(../)单独使用@alexgray 并不意味着什么,但是(../)#意味着尝试将括号内的模式扩展0次或多次,并且仅使用文件系统上实际存在的模式。因为我们正在从0扩展到n个父目录,所以我们有效地向上搜索到文件系统的根目录(请注意:您可能想在模式之后添加一些内容以使其有意义,但为了启发执行print -l (../)#)。并且#zsh是IRC频道,Faux是用户名。 Faux建议的解决方案,因此功劳。
2015年

1
那将扩大他们所有人。你可能想:(../)#.git(/Y1:a:h)停在第一个找到的一个(具有最新版本的zsh)
斯特凡Chazelas

1
非常有帮助,谢谢。要启用扩展的glob,请执行setopt extended_glob
Shlomi

0

此版本的查找支持“查找”语法,例如@sinelaw的答案,但也支持符号链接,而无需realpath。它还支持可选的“停止于”功能,因此可以正常工作:findup .:~ -name foo...搜索foo而不传递主目录。

#!/bin/bash

set -e

# get optional root dir
IFS=":" && arg=($1)
shift 1
path=${arg[0]}
root=${arg[1]}
[[ $root ]] || root=/
# resolve home dir
eval root=$root

# use "cd" to prevent symlinks from resolving
cd $path
while [[ "$cur" != "$root" && "$cur" != "/" ]];
do
    cur="$(pwd)"
    find  "$cur/"  -maxdepth 1 -mindepth 1 "$@"
    cd ..
done

0

我发现使用符号链接会杀死其他一些选项。特别是git的具体答案。我已经从这个非常有效的原始答案中创建了自己的最爱。

#!/usr/bin/env bash

# usage: upsearch .git

function upsearch () {
    origdir=${2-`pwd`}
    test / == "$PWD" && cd "$origdir" && return || \
    test -e "$1" && echo "$PWD" && cd "$origdir" && return || \
    cd .. && upsearch "$1" "$origdir"
}

我在go项目中使用了符号链接,因为go希望将源代码放在某个位置,并且我希望将项目保留在〜/ projects下。我在$ GOPATH / src中创建项目,并将它们符号链接到〜/ projects。因此,运行将git rev-parse --show-toplevel打印$ GOPATH目录,而不是〜/ projects目录。该解决方案解决了该问题。

我意识到这是一个非常具体的情况,但是我认为该解决方案很有价值。


0

Vincent Scheib的解决方案不适用于根目录中的文件。

以下版本支持,并且还允许您将起始目录作为第一个参数传递。

find-up() {
    path="$(realpath -s "$1")"

    while ! [ -e "$path"/"$2" ] && [ -n "$path" ]; do
        path="${path%/*}"
    done

    [ -e "$path"/"$2" ] && echo "$path"/"$2"
}

0

我将sinelaw的解决方案修改为POSIX。它不遵循符号链接,包括使用do while循环搜索根目录。

find-up:
#!/usr/bin/env sh

set -e # exit on error
# Use cd and pwd to not follow symlinks.
# Unlike find, default to current directory if no arguments given.
directory="$(cd "${1:-.}"; pwd)"

shift 1 # shift off the first argument, so only options remain

while :; do
  # POSIX, but gets "Permission denied" on private directories.
  # Use || true to allow errors without exiting on error.
  find "$directory" -path "${directory%/}/*/*" -prune -o ! -path "$directory" "$@" -print || true

  # Equivalent, but -mindepth and -maxdepth aren't POSIX.
  # find "$directory" -mindepth 1 -maxdepth 1 "$@"

  if [ "$directory" = '/' ]; then
    break # End do while loop
  else
    directory=$(dirname "$directory")
  fi
done
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.