入力引数がいくつかのファイルのフルパスであると仮定します。言う、
/abc/def/file1
/abc/def/ghi/file2
/abc/def/ghi/file3
/abc/def
bashシェルスクリプトでディレクトリ名を取得するにはどうすればよいですか?- 、、、およびのみを取得するにはどうすればよい
file1
ですか?/ghi/file2
/ghi/file3
入力引数がいくつかのファイルのフルパスであると仮定します。言う、
/abc/def/file1
/abc/def/ghi/file2
/abc/def/ghi/file3
/abc/def
bashシェルスクリプトでディレクトリ名を取得するにはどうすればよいですか?file1
ですか?/ghi/file2
/ghi/file3
パート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/.
また、呼び出しの詳細があります。
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()
{
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()
{
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
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番目の質問は簡単です。
これは、任意に複雑なファイル名(改行、バックスペースなどを含む)で機能することが示されているものです。
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"
}
以下の解決策ははるかに簡単であるように私には思えます。
前述のように、パート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/')
もちろん、これは1行で書き直すことができます。
CLOSEST_PARENT=$(printf "%s\n%s\n" "$file_1" "$file_2" | sed -e 'N;s/^\(.*\).*\n\1.*$/\1/' | sed 's/\(.*\)\/.*/\1/')
親のディレクトリを取得するには:
dirname /abc/def/file1
/ abc/defを与えます
そして、ファイル名を取得するには
basename /abc/def/file1
file1を与えます
そして、あなたの質問によると、最も近い親ディレクトリ名のみを使用する
basename $(dirname $(/abc/def/file1))
ここにdef 入力コードを与えます