bash shell脚本找到几个文件最近的父目录

时间:2012-09-09 16:37:30

标签: bash

假设输入参数是多个文件的FULL路径。说,

/abc/def/file1
/abc/def/ghi/file2
/abc/def/ghi/file3
  1. 如何在bash shell脚本中获取目录名/abc/def
  2. 我如何才能获得file1/ghi/file2/ghi/file3

5 个答案:

答案 0 :(得分:4)

鉴于第1部分(公共前缀)的答案,第2部分的答案是直截了当的;您将每个名称的前缀切片,这可以通过sed和其他选项完成。

然后,有趣的部分是找到共同的前缀。最小公共前缀为/(例如,/etc/passwd/bin/sh。最大公共前缀(根据定义)存在于所有字符串中,因此我们只需将其中一个字符串拆分为段,并将可能的前缀与其他字符串进行比较。概括地说:

split name A into components
known_prefix="/"
for each extra component from A
do
    possible_prefix="$known_prefix/$extra/"
    for each name
    do
        if $possible_prefix is not a prefix of $name
        then ...all done...break outer loop...
        fi
    done
    ...got here...possible prefix is a prefix!
    known_prefix=$possible_prefix
done

有一些管理细节要处理,例如名称中的空格。还有什么是允许的武器。问题标记为bash,但允许哪些外部命令(例如Perl)?

一个未定义的问题 - 假设名称列表为:

/abc/def/ghi
/abc/def/ghi/jkl
/abc/def/ghi/mno

是最长的共同前缀/abc/def还是/abc/def/ghi?我假设这里最长的公共前缀是/abc/def。 (如果您确实希望它为/abc/def/ghi,请使用/abc/def/ghi/.作为第一个名称。)

此外,还有调用详细信息:

  • 如何调用此函数或命令?
  • 如何返回值?
  • 这是一个还是两个函数或命令(longest_common_prefix和'path_without_prefix`)?

两个命令更容易:

  • prefix=$(longest_common_prefix name1 [name2 ...])
  • suffix=$(path_without_prefix /pre/fix /pre/fix/to/file [...])

path_without_prefix命令删除前缀(如果存在),如果前缀未启动名称,则保留参数不变。

longest_common_prefix

longest_common_prefix()
{
    declare -a names
    declare -a parts
    declare i=0

    names=("$@")
    name="$1"
    while x=$(dirname "$name"); [ "$x" != "/" ]
    do
        parts[$i]="$x"
        i=$(($i + 1))
        name="$x"
    done

    for prefix in "${parts[@]}" /
    do
        for name in "${names[@]}"
        do
            if [ "${name#$prefix/}" = "${name}" ]
            then continue 2
            fi
        done
        echo "$prefix"
        break
    done
}

测试:

set -- "/abc/def/file 0" /abc/def/file1 /abc/def/ghi/file2 /abc/def/ghi/file3 "/abc/def/ghi/file 4"
echo "Test: $@"
longest_common_prefix "$@"
echo "Test: $@" abc/def
longest_common_prefix "$@" abc/def
set --  /abc/def/ghi/jkl /abc/def/ghi /abc/def/ghi/mno
echo "Test: $@"
longest_common_prefix "$@"
set -- /abc/def/file1 /abc/def/ghi/file2 /abc/def/ghi/file3
echo "Test: $@"
longest_common_prefix "$@"
set -- "/a c/d f/file1" "/a c/d f/ghi/file2" "/a c/d f/ghi/file3"
echo "Test: $@"
longest_common_prefix "$@"

输出:

Test: /abc/def/file 0 /abc/def/file1 /abc/def/ghi/file2 /abc/def/ghi/file3 /abc/def/ghi/file 4
/abc/def
Test: /abc/def/file 0 /abc/def/file1 /abc/def/ghi/file2 /abc/def/ghi/file3 /abc/def/ghi/file 4 abc/def
Test: /abc/def/ghi/jkl /abc/def/ghi /abc/def/ghi/mno
/abc/def
Test: /abc/def/file1 /abc/def/ghi/file2 /abc/def/ghi/file3
/abc/def
Test: /a c/d f/file1 /a c/d f/ghi/file2 /a c/d f/ghi/file3
/a c/d f

path_without_prefix

path_without_prefix()
{
    local prefix="$1/"
    shift
    local arg
    for arg in "$@"
    do
        echo "${arg#$prefix}"
    done
}

测试:

for name in /pre/fix/abc /pre/fix/def/ghi /usr/bin/sh
do
    path_without_prefix /pre/fix $name
done

输出:

abc
def/ghi
/usr/bin/sh

答案 1 :(得分:2)

更“便携”的解决方案,因为它不使用特定于bash的功能:首先定义一个函数来计算两条路径的最长公共前缀:

function common_path()
{
  lhs=$1
  rhs=$2
  path=
  OLD_IFS=$IFS; IFS=/
  for w in $rhs; do
    test "$path" = / && try="/$w" || try="$path/$w"
    case $lhs in
      $try*) ;;
      *) break ;;
    esac
    path=$try
  done
  IFS=$OLD_IFS
  echo $path
}

然后将它用于一长串单词:

function common_path_all()
{
  local sofar=$1
  shift
  for arg
  do
    sofar=$(common_path "$sofar" "$arg")
  done
  echo ${sofar:-/}
}

根据您的输入,它会给出

$ common_path_all /abc/def/file1 /abc/def/ghi/file2 /abc/def/ghi/file3
/abc/def

正如Jonathan Leffler指出的那样,一旦你有了这个,第二个问题就是微不足道。

答案 2 :(得分:2)

Here's one已被证明与arbitrarily complex file names一起使用(包含换行符,退格等):

path_common() {
    if [ $# -ne 2 ]
    then
        return 2
    fi

    # Remove repeated slashes
    for param
    do
        param="$(printf %s. "$1" | tr -s "/")"
        set -- "$@" "${param%.}"
        shift
    done

    common_path="$1"
    shift

    for param
    do
        while case "${param%/}/" in "${common_path%/}/"*) false;; esac; do
            new_common_path="${common_path%/*}"
            if [ "$new_common_path" = "$common_path" ]
            then
                return 1 # Dead end
            fi
            common_path="$new_common_path"
        done
    done
    printf %s "$common_path"
}

答案 3 :(得分:1)

在我看来,下面的解决方案要简单得多。

如前所述,只有第1部分很棘手。第2部分很简单,有sed。

第1部分可分为2个子部分:

  1. 查找所有字符串的longest common prefix
  2. 确保此前缀是一个目录,如果没有修改它以获取相应的目录
  3. 可以使用以下代码完成。为了清楚起见,此示例仅使用2个字符串,但while循环为您提供了n个字符串所需的内容。

    library(splitstackshape)
    library(data.table)
    cSplit(setDT(df1, keep.rownames=TRUE), 'pages', ',', 'long')[,
       list(pages=toString(sort(pages)), sum.num.= sum.num.[1]) ,rn
       ][,list(Sum=sum(sum.num.)) , .(pages)]
     #                                 pages Sum
    #1:     Badezimmer, Baumarkt, Büromöbel  22
    #2:    Badezimmer, Baumarkt, Dekoration  14
    #3:          Badezimmer, Baumarkt, Flur  70
    #4:        Badezimmer, Baumarkt, Garten  18
    #5: Badezimmer, Baumarkt, Heimtextilien 100
    #6:        Badezimmer, Baumarkt, Kinder  28
    

    当然可以只用一行重写:

    LONGEST_PREFIX=$(printf "%s\n%s\n" "$file_1" "$file_2" | sed -e 'N;s/^\(.*\).*\n\1.*$/\1/')
    CLOSEST_PARENT=$(echo "$LONGEST_PREFIX" | sed 's/\(.*\)\/.*/\1/')
    

答案 4 :(得分:-2)

获取父母的目录:

  dirname /abc/def/file1

将提供 / abc / def

获取文件名

   basename /abc/def/file1

将提供 file1

根据您的问题,只使用最近的父目录名称

basename $(dirname $(/abc/def/file1))

将提供 def     在这里输入代码