web-dev-qa-db-ja.com

findの出力をキャプチャします。 -print0をbash配列に

find . -print0を使用することが、スペース、改行、引用符などを含むファイル名の可能性があるため、bashでファイルのリストを取得する唯一の安全な方法のようです。

ただし、bash内または他のコマンドラインユーティリティを使用して、findの出力を実際に有効にするのに苦労しています。出力を利用することができた唯一の方法は、それをPerlにパイプし、PerlのIFSをnullに変更することです。

find . -print0 | Perl -e '$/="\0"; @files=<>; print $#files;'

この例では、見つかったファイルの数を出力します。ファイル名の改行がカウントを破壊する危険を回避します。

find . | wc -l

ほとんどのコマンドラインプログラムはnull区切りの入力をサポートしていないため、上記のPerlスニペットで行ったように、bash配列でfind . -print0の出力をキャプチャするのが最良の方法だと思います。どんなタスクでも。

これどうやってするの?

これは機能しません:

find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )

より一般的な質問は次のとおりです:bashのファイルのリストを使用して便利なことを行うにはどうすればよいですか?

73
Idris

Greg's BashFAQ から恥知らずに盗まれた

_unset a i
while IFS= read -r -d $'\0' file; do
    a[i++]="$file"        # or however you want to process each file
done < <(find /tmp -type f -print0)
_

ここで使用されるリダイレクト構造(cmd1 < <(cmd2))は、より一般的なパイプライン(_cmd2 | cmd1_)と似ていますが、まったく同じではないことに注意してください-コマンドがシェル組み込みコマンド(たとえば、while)、パイプラインバージョンはサブシェルでそれらを実行し、それらが設定する変数(たとえば、配列a)は終了時に失われます。 cmd1 < <(cmd2)はサブシェルでcmd2のみを実行するため、配列はその構築後も存続します。警告:この形式のリダイレクトは、bashでのみ使用でき、sh-emulationモードではbashでも使用できません。 _#!/bin/bash_でスクリプトを開始する必要があります。

また、ファイル処理ステップ(この場合は_a[i++]="$file"_だけですが、ループ内でより洗練された何かをしたい場合があります)の入力がリダイレクトされるため、stdinから読み取るコマンドを使用できません。この制限を回避するために、私は使用する傾向があります:

_unset a i
while IFS= read -r -u3 -d $'\0' file; do
    a[i++]="$file"        # or however you want to process each file
done 3< <(find /tmp -type f -print0)
_

...これは、stdinではなくユニット3を介してファイルリストを渡します。

99
Gordon Davisson

多分あなたはxargsを探しています:

find . -print0 | xargs -r0 do_something_useful

オプション-L 1は、あなたにとっても有用かもしれません。これは、xargsを1つのファイル引数のみでdo_something_useful execします。

7
Balázs Pozsár

主な問題は、IFSにNUL値を割り当てることができないため、デリミタNUL(\ 0)がここでは役に立たないことです。優れたプログラマーとして、プログラムへの入力は処理可能なものであることに注意してください。

最初に、この部分を実行する小さなプログラムを作成します。

#!/bin/bash
printf "%s" "$@" | base64

...そしてbase64strと呼びます(chmod + xを忘れないでください)

次に、シンプルで簡単なforループを使用できます。

for i in `find -type f -exec base64str '{}' \;`
do 
  file="`echo -n "$i" | base64 -d`"
  # do something with file
done

だから、トリックは、base64文字列にbashのトラブルを引き起こす兆候がないことです-もちろんxxdまたは同様のものでも仕事をすることができます。

5
zstegi

ファイルをカウントするさらに別の方法:

find /DIR -type f -print0 | tr -dc '\0' | wc -c 
4
bitwise

Bash 4.4以降、組み込みのmapfileには-dスイッチがあり(readステートメントの-dスイッチと同様に区切り文字を指定するため)、区切り文字はnullバイトである。したがって、タイトルの質問に対するいい答え

find . -print0の出力をbash配列にキャプチャする

は:

mapfile -d '' ary < <(find . -print0)
3
gniourf_gniourf

これでカウントを安全に行うことができます:

find . -exec echo ';' | wc -l

(見つかったファイル/ディレクトリごとに改行を出力し、出力された改行をカウントします...)

2
Balázs Pozsár

次のことが可能な場合は、xargsを避けてください。

man Ruby | less -p 777 
IFS=$'\777' 
#array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' \; 2>/dev/null) ) 
array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' + 2>/dev/null) ) 
echo ${#array[@]} 
printf "%s\n" "${array[@]}" | nl 
echo "${array[0]}" 
IFS=$' \t\n' 
1
caruso

よりエレガントなソリューションが存在すると思いますが、私はこれを投げます。これは、スペースや改行を含むファイル名でも機能します:

i=0;
for f in *; do
  array[$i]="$f"
  ((i++))
done

その後、例えばファイルを1つずつリストします(この場合は逆順)。

for ((i = $i - 1; i >= 0; i--)); do
  ls -al "${array[$i]}"
done

このページ はニースの例を示しています。詳細は Advanced Bash-Scripting GuideChapter 26 をご覧ください。

1
Stephan202

私は新しいですが、これは答えだと思います。それが誰かを助けることを願っています:

STYLE="$HOME/.fluxbox/styles/"

declare -a array1

LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f`


echo $LISTING
array1=( `echo $LISTING`)
TAR_SOURCE=`echo ${array1[@]}`

#tar czvf ~/FluxieStyles.tgz $TAR_SOURCE
1
pete

古い質問ですが、誰もこの簡単な方法を提案しなかったので、私はそうするだろうと思いました。ファイル名にETXが含まれている場合は問題ありませんが、これで問題は解決しませんが、実際のシナリオには役立つと思われます。 nullを使用しようとすると、デフォルトのIFS処理ルールに違反するようです。検索オプションとエラー処理で好みに合わせて調整します。

savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"
0
Dennis Simpson

これはStephan202のバージョンに似ていますが、ファイル(およびディレクトリ)は一度に配列に入れられます。ここでのforループは、単に「役に立つことをする」ためのものです。

files=(*)                        # put files in current directory into an array
i=0
for file in "${files[@]}"
do
    echo "File ${i}: ${file}"    # do something useful 
    let i++
done

カウントを取得するには:

echo ${#files[@]}
0

Gordon Davissonの答えはbashに最適です。ただし、zshユーザーには便利なショートカットがあります。

まず、変数に文字列を配置します。

A="$(find /tmp -type f -print0)"

次に、この変数を分割して配列に保存します。

B=( ${(s/^@/)A} )

トリックがあります:^@はNUL文字です。そのためには、Ctrl + Vに続けてCtrl + @を入力する必要があります。

$ Bの各エントリに正しい値が含まれていることを確認できます。

for i in "$B[@]"; echo \"$i\"

注意深い読者であれば、ほとんどの場合、findコマンドの呼び出しは**構文。例えば:

B=( /tmp/** )
0
Jezz