Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/bash/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
bash shell脚本以查找几个文件的最近父目录_Bash - Fatal编程技术网

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

bash shell脚本以查找几个文件的最近父目录,bash,Bash,假设输入参数是多个文件的完整路径。说 /abc/def/file1 /abc/def/ghi/file2 /abc/def/ghi/file3 如何在bashshell脚本中获取目录名/abc/def 如何仅获取file1、/ghi/file2和/ghi/file3 要获取父目录,请执行以下操作: dirname /abc/def/file1 将给出/abc/def 和获取文件名 basename /abc/def/file1 将给出文件1 根据您的问题,仅获取最近的父目录名称使

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

/abc/def/file1
/abc/def/ghi/file2
/abc/def/ghi/file3
  • 如何在bashshell脚本中获取目录名
    /abc/def
  • 如何仅获取
    file1
    /ghi/file2
    /ghi/file3

  • 要获取父目录,请执行以下操作:

      dirname /abc/def/file1
    
    将给出/abc/def

    和获取文件名

       basename /abc/def/file1
    
    将给出文件1

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

    basename $(dirname $(/abc/def/file1))
    
    将提供def
    在此处输入代码

    给定第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\u common\u prefix
      和“path\u without\u prefix”)
    两个命令更简单:

    • prefix=$(最长的\u公共\u前缀name1[name2…])
    • suffix=$(路径\不带\前缀/pre/fix/pre/fix/to/file[…])
    如果前缀存在,则
    path\u without\u 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
    
    abc
    def/ghi
    /usr/bin/sh
    
    不带前缀的路径 测试:

    输出:

    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
    
    abc
    def/ghi
    /usr/bin/sh
    
    一个更“可移植”的解决方案,从某种意义上说,它不使用特定于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所指出的,一旦你有了这个问题,第二个问题就无关紧要了。

    这已经被证明是可以使用的(包括换行符、退格等):


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

    如前所述,只有第1部分比较棘手。第2部分简单介绍了sed

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

  • 查找所有字符串的
  • 确保此前缀是一个目录,如果没有,则修剪它以获得相应的目录
  • 它可以通过以下代码完成。为清楚起见,本例仅使用2个字符串,但while循环会提供您想要的n个字符串

    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/')
    
    这当然可以在一行中重写:

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

    请注意,此解决方案假定路径名是绝对的。如果路径名是相对的(即
    /abc/def/file1
    /abc/def/file2/
    ),最长的公共前缀将以无限循环失败。通过将条件
    [“$x”!=“/”]
    更改为
    [“$x”!=“/”-a“$x”!=”]
    ,可以轻松修复此问题以适应相对路径。
    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"
    }
    
    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/')
    
    CLOSEST_PARENT=$(printf "%s\n%s\n" "$file_1" "$file_2" | sed -e 'N;s/^\(.*\).*\n\1.*$/\1/'  | sed 's/\(.*\)\/.*/\1/')