130

特定のディレクトリのすべての子ディレクトリ (ファイルではない) をループする bash シェル スクリプトがあります。問題は、一部のディレクトリ名にスペースが含まれていることです。

私のテストディレクトリの内容は次のとおりです。

$ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

そして、ディレクトリをループするコード:

for f in `find test/* -type d`; do
  echo $f
done

出力は次のとおりです。

テスト/ボルチモア
テスト/チェリー
丘
テスト/エジソン
テスト/新規
ヨーク
街
テスト/フィラデルフィア

Cherry Hill と New York City は、2 つまたは 3 つの別々のエントリとして扱われます。

次のように、ファイル名を引用してみました。

for f in `find test/* -type d | sed -e 's/^/\"/' | sed -e 's/$/\"/'`; do
  echo $f
done

しかし、役に立たない。

これを行う簡単な方法が必要です。


以下の答えは素晴らしいです。しかし、これをさらに複雑にするために、テストディレクトリにリストされているディレクトリを常に使用したいとは限りません。代わりに、ディレクトリ名をコマンドライン パラメータとして渡したい場合があります。

IFSを設定するというチャールズの提案を受けて、次のことを思いつきました。

dirlist="${@}"
(
  [[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n'
  for d in $dirlist; do
    echo $d
  done
)

これは、コマンド ライン引数にスペースが含まれていない限り (それらの引数が引用符で囲まれていても) 正常に機能します。たとえば、次のようにスクリプトを呼び出すとtest.sh "Cherry Hill" "New York City"、次の出力が生成されます。

チェリー
丘
新しい
ヨーク
街
4

20 に答える 20

107

まず、そのようにしないでください。最善のアプローチは、find -exec適切に使用することです。

# this is safe
find test -type d -exec echo '{}' +

もう 1 つの安全な方法は、NUL で終わるリストを使用することですが、これには find サポートが必要です-print0

# this is safe
while IFS= read -r -d '' n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d -print0)

また、find から配列を生成し、後でその配列を渡すこともできます。

# this is safe
declare -a myarray
while IFS= read -r -d '' n; do
  myarray+=( "$n" )
done < <(find test -mindepth 1 -type d -print0)
printf '%q\n' "${myarray[@]}" # printf is an example; use it however you want

検索が をサポートしていない場合-print0、結果は安全ではありません。名前に改行を含むファイルが存在する場合、以下は期待どおりに動作しません (これは正当です)。

# this is unsafe
while IFS= read -r n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d)

上記のいずれかを使用しない場合、3 番目のアプローチ (単語分割を行う前にサブプロセスの出力全体を読み取るため、時間とメモリ使用量の両方の点で効率が低下します) は、IFS変数を使用することです。スペース文字が含まれていません。グロビング ( ) をオフにして、やset -fなどのグロブ文字を含む文字列が展開されないようにします。[]*?

# this is unsafe (but less unsafe than it would be without the following precautions)
(
 IFS=$'\n' # split only on newlines
 set -f    # disable globbing
 for n in $(find test -mindepth 1 -type d); do
   printf '%q\n' "$n"
 done
)

最後に、コマンド ライン パラメーターの場合、シェルが配列をサポートしている場合 (つまり、ksh、bash、または zsh)、配列を使用する必要があります。

# this is safe
for d in "$@"; do
  printf '%s\n' "$d"
done

分離を維持します。引用 (および$@ではなくの使用$*) が重要であることに注意してください。配列は、グロブ式など、他の方法でも設定できます。

# this is safe
entries=( test/* )
for d in "${entries[@]}"; do
  printf '%s\n' "$d"
done
于 2008-11-19T05:19:41.220 に答える
28
find . -type d | while read file; do echo $file; done

ただし、ファイル名に改行が含まれている場合は機能しません。上記は、実際にディレクトリ名を変数に入れたいときに私が知っている唯一の解決策です。コマンドを実行したいだけなら、xargs を使用します。

find . -type d -print0 | xargs -0 echo 'The directory is: '
于 2008-11-19T05:21:19.343 に答える
24

ファイル名のタブや空白を処理する簡単なソリューションを次に示します。ファイル名に改行などの他の奇妙な文字を処理する必要がある場合は、別の回答を選択してください。

テストディレクトリ

ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

ディレクトリに入るコード

find test -type d | while read f ; do
  echo "$f"
done

"$f"引数として使用する場合、ファイル名を引用符 ( ) で囲む必要があります。引用符がない場合、スペースは引数区切りとして機能し、呼び出されたコマンドに複数の引数が与えられます。

そして出力:

test/Baltimore
test/Cherry Hill
test/Edison
test/New York City
test/Philadelphia
于 2009-09-23T09:01:43.937 に答える
7

これは標準の Unix では非常にトリッキーであり、ほとんどのソリューションは改行やその他の文字に違反しています。ただし、GNU ツール セットを使用している場合は、findオプション-print0を利用xargsして、対応するオプション-0(マイナス ゼロ) と一緒に使用できます。単純なファイル名に使用できない文字が 2 つあります。それらはスラッシュと NUL '\0' です。明らかに、スラッシュはパス名に表示されるため、NUL '\0' を使用して名前の終わりをマークする GNU ソリューションは、独創的で簡単です。

于 2008-11-19T05:45:43.553 に答える
5

次を使用して一時的にIFS(内部フィールドセパレーター)を使用できます。

OLD_IFS=$IFS     # Stores Default IFS
IFS=$'\n'        # Set it to line break
for f in `find test/* -type d`; do
    echo $f
done

IFS=$OLD_IFS

<!>

于 2016-03-09T08:05:00.857 に答える
4
find . -print0|while read -d $'\0' file; do echo "$file"; done
于 2012-03-15T16:38:49.033 に答える
4

私が使う

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in $( find "$1" -type d ! -path "$1" )
do
  echo $f
done
IFS=$SAVEIFS

それで十分ではないでしょうか?http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html
からのアイデア

于 2012-06-10T14:26:06.370 に答える
4

なぜ入れないのか

IFS='\n'

for コマンドの前に? これにより、フィールド区切り文字が < Space>< Tab>< Newline> から < Newline> に変更されます

于 2012-02-26T11:07:26.427 に答える
4

リストを文字列として保存しないでください。この区切り文字の混乱を避けるために、それらを配列として保存してください。test のすべてのサブディレクトリ、またはコマンド ラインで指定されたリストのいずれかで動作するスクリプトの例を次に示します。

#!/bin/bash
if [ $# -eq 0 ]; then
        # if no args supplies, build a list of subdirs of test/
        dirlist=() # start with empty list
        for f in test/*; do # for each item in test/ ...
                if [ -d "$f" ]; then # if it's a subdir...
                        dirlist=("${dirlist[@]}" "$f") # add it to the list
                fi
        done
else
        # if args were supplied, copy the list of args into dirlist
        dirlist=("$@")
fi
# now loop through dirlist, operating on each one
for dir in "${dirlist[@]}"; do
        printf "Directory: %s\n" "$dir"
done

では、カーブを 1 つまたは 2 つ挿入したテスト ディレクトリでこれを試してみましょう。

$ ls -F test
Baltimore/
Cherry Hill/
Edison/
New York City/
Philadelphia/
this is a dirname with quotes, lfs, escapes: "\''?'?\e\n\d/
this is a file, not a directory
$ ./test.sh 
Directory: test/Baltimore
Directory: test/Cherry Hill
Directory: test/Edison
Directory: test/New York City
Directory: test/Philadelphia
Directory: test/this is a dirname with quotes, lfs, escapes: "\''
'
\e\n\d
$ ./test.sh "Cherry Hill" "New York City"
Directory: Cherry Hill
Directory: New York City
于 2009-04-27T18:50:45.280 に答える
3

ps入力のスペースのみについての場合、いくつかの二重引用符はスムーズに機能しました...

read artist;

find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 '{}' \;
于 2012-11-03T23:48:30.950 に答える
2

Jonathanの発言に追加するには、次のよう-print0に for オプションをfind組み合わせて使用​​しxargsます。

find test/* -type d -print0 | xargs -0 command

commandこれにより、適切な引数でコマンドが実行されます。スペースを含むディレクトリは適切に引用符で囲まれます (つまり、1 つの引数として渡されます)。

于 2008-11-19T05:53:32.853 に答える
1

ファイルリストをBash配列に変換します。これは、Bash関数から配列を返すためのMatt McClureのアプローチを使用しています。http: //notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html 結果は次のとおりです。複数行の入力をBash配列に変換します。

#!/bin/bash

# This is the command where we want to convert the output to an array.
# Output is: fileSize fileNameIncludingPath
multiLineCommand="find . -mindepth 1 -printf '%s %p\\n'"

# This eval converts the multi-line output of multiLineCommand to a
# Bash array. To convert stdin, remove: < <(eval "$multiLineCommand" )
eval "declare -a myArray=`( arr=(); while read -r line; do arr[${#arr[@]}]="$line"; done; declare -p arr | sed -e 's/^declare -a arr=//' ) < <(eval "$multiLineCommand" )`"

for f in "${myArray[@]}"
do
   echo "Element: $f"
done

このアプローチは、不正な文字が存在する場合でも機能するように見え、入力をBash配列に変換する一般的な方法です。欠点は、入力が長い場合、Bashのコマンドラインサイズの制限を超えたり、大量のメモリを消費したりする可能性があることです。

最終的にリストで機能するループにもリストがパイプされるアプローチには、stdinの読み取りが容易ではないという欠点があり(ユーザーに入力を求めるなど)、ループは新しいプロセスであるため、なぜ変数が不思議に思うかもしれません。ループ内で設定したものは、ループの終了後は使用できません。

IFSの設定も嫌いです。他のコードを台無しにする可能性があります。

于 2012-09-17T21:10:41.297 に答える
1
#!/bin/bash

dirtys=()

for folder in *
do    
 if [ -d "$folder" ]; then    
    dirtys=("${dirtys[@]}" "$folder")    
 fi    
done    

for dir in "${dirtys[@]}"    
do    
   for file in "$dir"/\*.mov   # <== *.mov
   do    
       #dir_e=`echo "$dir" | sed 's/[[:space:]]/\\\ /g'`   -- This line will replace each space into '\ '   
       out=`echo "$file" | sed 's/\(.*\)\/\(.*\)/\2/'`     # These two line code can be written in one line using multiple sed commands.    
       out=`echo "$out" | sed 's/[[:space:]]/_/g'`    
       #echo "ffmpeg -i $out_e -sameq -vcodec msmpeg4v2 -acodec pcm_u8 $dir_e/${out/%mov/avi}"    
       `ffmpeg -i "$file" -sameq -vcodec msmpeg4v2 -acodec pcm_u8 "$dir"/${out/%mov/avi}`    
   done    
done

上記のコードは、.mov ファイルを .avi に変換します。.mov ファイルは別のフォルダーにあり、フォルダー名にも空白が含まれています。上記のスクリプトは、.mov ファイルを同じフォルダー内の .avi ファイルに変換します。それがあなたの民を助けるかどうかはわかりません。

場合:

[sony@localhost shell_tutorial]$ ls
Chapter 01 - Introduction  Chapter 02 - Your First Shell Script
[sony@localhost shell_tutorial]$ cd Chapter\ 01\ -\ Introduction/
[sony@localhost Chapter 01 - Introduction]$ ls
0101 - About this Course.mov   0102 - Course Structure.mov
[sony@localhost Chapter 01 - Introduction]$ ./above_script
 ... successfully executed.
[sony@localhost Chapter 01 - Introduction]$ ls
0101_-_About_this_Course.avi  0102_-_Course_Structure.avi
0101 - About this Course.mov  0102 - Course Structure.mov
[sony@localhost Chapter 01 - Introduction]$ CHEERS!

乾杯!

于 2010-11-18T11:54:02.500 に答える
1

パス名の空白も処理する必要がありました。私が最終的にやったことは、再帰とを使用することでしたfor item in /path/*:

function recursedir {
    local item
    for item in "${1%/}"/*
    do
        if [ -d "$item" ]
        then
            recursedir "$item"
        else
            command
        fi
    done
}
于 2009-07-10T18:52:01.463 に答える
0
find Downloads -type f | while read file; do printf "%q\n" "$file"; done
于 2016-01-15T09:55:28.027 に答える
0

私の質問とあなたの質問の間にいくつかの類似点があることがわかりました。明らかに、引数をコマンドに渡したい場合

test.sh "Cherry Hill" "New York City"

それらを順番に印刷する

for SOME_ARG in "$@"
do
    echo "$SOME_ARG";
done;

$@ が二重引用符で囲まれていることに注意してください。

于 2009-05-25T09:12:04.490 に答える
0

特定のフォルダーから複数のディレクトリまたはファイルを順次圧縮するには、同じ概念が必要でした。awk を使用して ls からリストを解析し、名前の空白の問題を回避することを解決しました。

source="/xxx/xxx"
dest="/yyy/yyy"

n_max=`ls . | wc -l`

echo "Loop over items..."
i=1
while [ $i -le $n_max ];do
item=`ls . | awk 'NR=='$i'' `
echo "File selected for compression: $item"
tar -cvzf $dest/"$item".tar.gz "$item"
i=$(( i + 1 ))
done
echo "Done!!!"

どう思いますか?

于 2013-08-24T19:46:24.747 に答える
-3

私にとってこれは機能し、ほとんど「クリーン」です。

for f in "$(find ./test -type d)" ; do
  echo "$f"
done
于 2011-07-13T04:28:12.357 に答える
-4

単純なバリアントの問題がありました... 型指定された .flv のファイルを .mp3 (あくび) に変換します。

for file in read `find . *.flv`; do ffmpeg -i ${file} -acodec copy ${file}.mp3;done

すべての Macintosh ユーザーのフラッシュ ファイルを再帰的に検索し、それらをオーディオに変換します (コピー、トランスコードなし) ... 上記の while のように、'for file in ' の代わりに read がエスケープされることに注意してください。

于 2010-04-21T19:19:28.247 に答える