13

入力引数がいくつかのファイルのフルパスであると仮定します。言う、

/abc/def/file1
/abc/def/ghi/file2
/abc/def/ghi/file3
  1. /abc/defbashシェルスクリプトでディレクトリ名を取得するにはどうすればよいですか?
  2. 、、、およびのみを取得するにはどうすればよいfile1ですか?/ghi/file2/ghi/file3
4

5 に答える 5

5

パート1(共通のプレフィックス)の答えを考えると、パート2の答えは簡単です。各名前からプレフィックスを切り取ります。これはsed、他のオプションの中でも特に可能です。

したがって、興味深い部分は、共通のプレフィックスを見つけることです。最小の共通プレフィックスは/(たとえば、/etc/passwdおよびなど)です。/bin/sh最大の共通プレフィックスは(定義上)すべての文字列に存在するため、文字列の1つをセグメントに分割し、可能なプレフィックスを他の文字列と比較する必要があります。概要:

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など)?

未定義の問題の1つ—名前のリストが次のとおりだったとします。

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

最長の共通プレフィックス/abc/defですか/abc/def/ghi?ここで最も長い共通プレフィックスはであると仮定します/abc/def。(本当に必要な場合は、最初の名前に/abc/def/ghi使用してください。)/abc/def/ghi/.

また、呼び出しの詳細があります。

  • この関数またはコマンドはどのように呼び出されますか?
  • 値はどのように返されますか?
  • これは1つまたは2つの関数またはコマンド(longest_common_prefixおよび'path_without_prefix`)ですか?

2つのコマンドの方が簡単です。

  • 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
于 2012-09-09T17:37:35.580 に答える
2

bash固有の機能を使用しないという意味で、より「ポータブル」なソリューション:最初に、2つのパスの最長の共通プレフィックスを計算する関数を定義します。

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

ジョナサン・レフラーが指摘したように、それができたら、2番目の質問は簡単です。

于 2012-09-09T21:17:55.253 に答える
2

これは、任意に複雑なファイル名(改行、バックスペースなどを含む)で機能することが示されているものです。

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"
}
于 2012-09-11T11:27:14.500 に答える
1

以下の解決策ははるかに簡単であるように私には思えます。

前述のように、パート1だけが注意が必要です。パート2はsedで簡単です。

パート1は2つのサブパートにカットできます:

  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/')

もちろん、これは1行で書き直すことができます。

CLOSEST_PARENT=$(printf "%s\n%s\n" "$file_1" "$file_2" | sed -e 'N;s/^\(.*\).*\n\1.*$/\1/'  | sed 's/\(.*\)\/.*/\1/')
于 2015-11-07T18:09:45.353 に答える
-1

親のディレクトリを取得するには:

  dirname /abc/def/file1

/ abc/defを与えます

そして、ファイル名を取得するには

   basename /abc/def/file1

file1を与えます

そして、あなたの質問によると、最も近い親ディレクトリ名のみを使用する

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

ここにdef 入力コードを与えます

于 2012-09-09T16:44:04.237 に答える