web-dev-qa-db-ja.com

おそらく空白でファイル名のリストを操作するPOSIX準拠の方法

空白を含むファイル名を操作するために配列を使用することを提案するBashスクリプトガイドを見てきました。 DashAsBinSh ただし、配列は移植できないことを示唆しているため、空白を含む可能性のあるファイル名のリストを扱うPOSIX準拠の方法を探しています。

以下のスクリプト例を変更して、echoになるようにしています。

foo/target/a.jar
foo/target/b.jar
bar/target/lol whitespace.jar

これがスクリプトです

#!/usr/bin/env sh

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"
# this would be produced by a 'ls' command
# We can execute the ls within the script, if it helps

dostuffwith() { echo $1; };

F_LOCATIONS=$INPUT
ALL_FILES=$(for f in $F_LOCATIONS; do echo `basename $f`; done)
ALL_FILES=$(echo "$ALL_FILES" | sort | uniq)

for f in $ALL_FILES
do
    fpath=$(echo "$F_LOCATIONS" | grep -m1 $f)
    dostuffwith $fpath
done
14
Eero Aaltonen

POSIXシェルには1つの配列があります:位置パラメータ($1$2など。まとめて"$@"と呼ばれます)。

set -- 'foo/target/a.jar' 'foo/target/b.jar' 'bar/target/b.jar' 'bar/target/lol whitespace.jar'
set -- "$@" '/another/one at the end.jar'
…
for jar do
  dostuffwith "$jar"
done

これは1つしか存在しないので不便であり、位置パラメータのその他の使用はすべて破棄されます。位置パラメータは関数に対してローカルです。これは、時には祝福であり、時には呪いです。

ファイル名に改行が含まれないことが保証されている場合は、改行を区切り文字として使用できます。変数を展開するときは、最初にset -fでグロビングをオフにし、フィールド分割文字IFSのリストに改行のみが含まれるように設定します。

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"
…
set -f; IFS='
'                           # turn off variable value expansion except for splitting at newlines
for jar in $INPUT; do
  set +f; unset IFS
  dostuffwith "$jar"        # restore globbing and field splitting at all whitespace
done
set +f; unset IFS           # do it again in case $INPUT was empty

リスト内の項目が改行で区切られているため、多くのテキスト処理コマンド、特にsortを便利に使用できます。

明示的にフィールド分割を実行したい場合(およびオフにしていない限りグロブも)を除き、常に変数の引用符を二重引用符で囲むことを忘れないでください。

$INPUT変数は改行を区切り文字として使用するため、ファイルの名前に改行が含まれないと想定します。そのため、はい、ファイルを反復して空白を保持する簡単な方法があります。

ビルトインreadシェルを使用するのが目的です。通常、readはすべての空白で分割されるため、空白で分割されます。ただし、IFS=$'\n'を設定すると、代わりに改行のみで分割されます。したがって、リストの各行を反復処理できます。

これが私が思いつくことができる最小のソリューションです:

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"

dostuffwith() {
    echo "$1"
}

echo "$INPUT" | awk -F/ '{if (!seen[$NF]++) print }' | \
while IFS=$'\n' read file; do
  dostuffwith "$file"
done

基本的には、「$ INPUT」をawkに送信します。これは、ファイル名に基づいて重複排除します(/で分割され、最後の項目が以前に表示されていない場合は行を出力します)。次に、awkがファイルパスのリストを生成したら、while readを使用してリストを反復処理します。

5
Patrick