tìm kiếm trong thư mục mẹ thay vì thư mục con


45

Tôi được lồng sâu trong một cây tệp và tôi muốn tìm thư mục cha nào chứa tệp.

Ví dụ: Tôi đang ở trong một tập hợp các kho Git lồng nhau và muốn tìm thư mục .git kiểm soát các tệp tôi hiện đang ở. Tôi hy vọng cho một cái gì đó như find -searchup -iname ".git"

Câu trả lời:


16

Một phiên bản thậm chí còn chung hơn cho phép sử dụng findcác tùy chọn:

#!/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

Ví dụ: (giả sử tập lệnh được lưu dưới dạng find_up.sh)

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

... sẽ in tên của tất cả các some_dirtổ tiên (bao gồm cả chính nó) cho đến khi /tìm thấy một tệp có mẫu.

Khi sử dụng readlink -fđoạn script trên sẽ theo các liên kết tượng trưng trên đường lên, như đã lưu ý trong các bình luận. realpath -sThay vào đó, bạn có thể sử dụng , nếu bạn muốn theo dõi các đường dẫn theo tên ("/ foo / bar" sẽ chuyển lên "foo" ngay cả khi "thanh" là một liên kết tượng trưng) - tuy nhiên yêu cầu cài đặt realpathkhông được cài đặt theo mặc định trên hầu hết các nền tảng.


rắc rối với những điều này là cách nó giải quyết các liên kết. ví dụ: nếu tôi đang ở ~ / foo / bar và thanh là một liên kết tượng trưng đến / some / other / place, thì ~ / foo và ~ / sẽ không bao giờ được tìm kiếm.
Erik Aronesty

@ErikAronesty, bạn đã đúng - đã cập nhật câu trả lời. Bạn có thể sử dụng realpath -sđể có được hành vi bạn muốn.
sinelaw

hoạt động khi bạn chỉ định một đường dẫn đầy đủ để tìm kiếm. thật đáng buồn, trên centos 5,8, realpath -s <dir> có một lỗi trong đó nếu <dir> là tương đối, thì dù sao nó cũng giải quyết được các liên kết tượng trưng ... mặc dù đó là tài liệu.
Erik Aronesty

readlinkkhông phải là một phần của tiêu chuẩn. Một tập lệnh di động có thể được thực hiện chỉ với các tính năng shell POSIX.
schily

1
FYI, điều này không hoạt động trong zsh vì nó xác định lại đường dẫn $ để shell không thể tìm thấy findhoặc readlink. Tôi đề nghị thay đổi nó thành một cái gì đó giống như $curpaththay vào đó để nó không làm mờ biến shell.
Skyler

28
git rev-parse --show-toplevel

sẽ in ra thư mục cấp cao nhất của kho lưu trữ hiện tại, nếu bạn đang ở trong một.

Các tùy chọn liên quan khác:

# `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
Điều này chỉ hoạt động cho git chính nó. Câu hỏi chung chung hơn.
alex

1
Điều này sẽ không hoạt động trong một repo trần,
Jason Pyeron

19

Một phiên bản tổng quát của câu trả lời của Gilles, tham số đầu tiên được sử dụng để tìm kết quả khớp:

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

Giữ việc sử dụng các liên kết sym.


15

Tìm không thể làm điều đó. Tôi không thể nghĩ bất cứ điều gì đơn giản hơn một vòng lặp shell. (Chưa được kiểm tra, giả sử là không có /.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

Đối với trường hợp cụ thể của kho lưu trữ git, bạn có thể để git thực hiện công việc cho bạn.

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

1
Thật tuyệt, điều này đặt tôi vào một con đường cho một giải pháp chung - mà tôi đăng như một câu trả lời khác ở đây.
Vincent Scheib

12

Nếu bạn đang sử dụng zsh với tính năng mở rộng toàn cầu được bật, bạn có thể làm điều đó với một 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'

Giải thích (trích từ man zshexpn):

Globbing đệ quy

Một thành phần tên đường dẫn của biểu mẫu (foo/)#khớp với một đường dẫn bao gồm 0 hoặc nhiều thư mục khớp với mẫu foo. Như một tốc ký, **/tương đương với (*/)#.

Công cụ sửa đổi

Sau trình chỉ định từ tùy chọn, bạn có thể thêm một chuỗi gồm một hoặc nhiều từ bổ nghĩa sau, mỗi từ đứng trước ':'. Các công cụ sửa đổi này cũng hoạt động dựa trên kết quả của việc tạo tên tệp và mở rộng tham số, trừ khi được ghi chú.

  • một
    • Biến tên tệp thành một đường dẫn tuyệt đối: chuẩn bị thư mục hiện tại, nếu cần và giải quyết mọi sử dụng '..' và '.'
  • Một
    • Là ' a ', nhưng cũng giải quyết việc sử dụng các liên kết tượng trưng nếu có thể. Lưu ý rằng độ phân giải của '..' xảy ra trước khi phân giải các liên kết tượng trưng. Cuộc gọi này tương đương với một trừ khi hệ thống của bạn có realpathcuộc gọi hệ thống (hệ thống hiện đại làm).
  • h
    • Xóa một thành phần tên đường dẫn, rời khỏi đầu. Điều này hoạt động như ' dirname'.

Tín dụng: Fauxbật #zshcho đề xuất ban đầu của việc sử dụng (../)#.git(:h).


Điều này thật tuyệt vời ... Nếu tôi chỉ có thể tìm ra (gợi ý: bạn nên nói với tôi ) những gì (../)đang làm ... Ồ, và ai / cái gì Faux on #zsh?
alex xám

1
@alexgray (../)một mình không có nghĩa gì nhiều, nhưng (../)#có nghĩa là cố gắng mở rộng mẫu bên trong dấu ngoặc đơn 0 lần trở lên và chỉ sử dụng các mẫu thực sự tồn tại trên hệ thống tệp. Vì chúng tôi đang mở rộng từ 0 đến n thư mục mẹ, chúng tôi tìm kiếm một cách hiệu quả đến thư mục gốc của hệ thống tệp (lưu ý: bạn có thể muốn thêm một cái gì đó sau mẫu để làm cho nó có ý nghĩa, nhưng để thực hiện khai sáng print -l (../)#). Và #zshlà một kênh IRC, Fauxlà một tên người dùng trên đó. Fauxđề xuất giải pháp, do đó tín dụng.
suy nghĩ

1
Điều đó sẽ mở rộng tất cả chúng. Bạn có thể muốn: (../)#.git(/Y1:a:h)dừng lại ở lần tìm thấy đầu tiên (với phiên bản gần đây của zsh)
Stéphane Chazelas

1
Rất hữu ích, cảm ơn. Để kích hoạt tính năng mở rộng toàn cầusetopt extended_glob
Shlomi

0

Phiên bản tìm kiếm này hỗ trợ cú pháp "tìm", như câu trả lời của @ sinelaw, nhưng cũng hỗ trợ các liên kết tượng trưng mà không cần đến giao diện. Nó cũng hỗ trợ tính năng "dừng tại" tùy chọn, do đó, tính năng này hoạt động: findup .:~ -name foo... tìm kiếm foo mà không cần chuyển qua thư mục nhà.

#!/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

Tôi đã thấy rằng làm việc với các liên kết tượng trưng sẽ giết chết một số tùy chọn khác. Đặc biệt là câu trả lời cụ thể của git. Tôi đã tạo ra một nửa yêu thích của riêng mình từ câu trả lời ban đầu này khá hiệu quả.

#!/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"
}

Tôi đang sử dụng symlink cho các dự án đi của mình vì go muốn mã nguồn ở một vị trí nhất định và tôi muốn giữ các dự án của mình dưới ~ / dự án. Tôi tạo dự án bằng $ GOPATH / src và liên kết chúng với ~ / dự án. Vì vậy, chạy git rev-parse --show-toplevelin ra thư mục $ GOPATH, không phải thư mục ~ / dự án. Giải pháp này giải quyết vấn đề đó.

Tôi nhận ra đây là một tình huống rất cụ thể, nhưng tôi nghĩ giải pháp này có giá trị.


0

Giải pháp của Vincent Scheib không hoạt động đối với các tệp nằm trong thư mục gốc.

Phiên bản sau đây, và cũng cho phép bạn vượt qua thư mục bắt đầu làm đối số thứ nhất.

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

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

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

0

Tôi đã sửa đổi giải pháp của sinelaw thành POSIX. Nó không theo liên kết tượng trưng và bao gồm tìm kiếm thư mục gốc bằng cách sử dụng vòng lặp 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.