特定のディレクトリのすべての子ディレクトリ(ただし、ファイルではない)をループする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
出力は次のとおりです。
test/Baltimore test/Cherry Hill test/Edison test/New York City test/Philadelphia
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を設定するというCharlesの提案を受けて、次のことを思いつきました。
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"
は次の出力を生成します。
チェリー ヒル 新規 ヨーク 市
まず、そのようにしないでください。最適なアプローチは、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番目の方法(Word分割を行う前にサブプロセスの出力全体を読み取るため、時間とメモリ使用量の両方の面で効率が低下します)は、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
find . -type d | while read file; do echo $file; done
ただし、ファイル名に改行が含まれる場合は機能しません。上記は、実際に変数にディレクトリ名を持ちたいときに知っている唯一の解決策です。何らかのコマンドを実行するだけの場合は、xargsを使用します。
find . -type d -print0 | xargs -0 echo 'The directory is: '
ファイル名のタブや空白を処理する簡単なソリューションを次に示します。改行のようなファイル名の他の奇妙な文字を処理する必要がある場合は、別の答えを選んでください。
テストディレクトリ
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
これは標準のUnixでは非常にトリッキーであり、ほとんどのソリューションは改行または他の文字のファウルを実行します。ただし、GNUツールセットを使用している場合は、find
オプション-print0
を活用し、xargs
を対応するオプション-0
(マイナスゼロ)。単純なファイル名には表示できない2つの文字があります。それらはスラッシュとNUL '\ 0'です。明らかに、パス名にはスラッシュが含まれているため、NUL '\ 0'を使用して名前の終わりを示すGNUソリューションは独創的であり、誰にもわかりません。
次を使用して、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
なぜ置くだけではない
IFS='\n'
forコマンドの前に?これにより、フィールド区切り文字が<スペース> <タブ> <改行>から<改行>に変更されます
私が使う
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 からのアイデア
リストを文字列として保存しないでください。この区切り記号の混乱を避けるために、それらを配列として保存します。以下は、テストのすべてのサブディレクトリで動作するスクリプトの例、またはコマンドラインで提供されるリストです。
#!/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
find . -print0|while read -d $'\0' file; do echo "$file"; done
ps入力のスペースについてのみの場合、いくつかの二重引用符はスムーズに機能しました...
read artist;
find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 '{}' \;
Jonathan の説明に追加するには、次のようにfind
とともにxargs
に-print0
オプションを使用します。
find test/* -type d -print0 | xargs -0 command
適切な引数を指定してコマンドcommand
を実行します。スペースが含まれるディレクトリは適切に引用されます(つまり、1つの引数として渡されます)。
パス名の空白も処理する必要がありました。最後にしたことは、再帰とfor item in /path/*
を使用することでした:
function recursedir {
local item
for item in "${1%/}"/*
do
if [ -d "$item" ]
then
recursedir "$item"
else
command
fi
done
}
#!/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!
乾杯!
ファイルリストを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の設定も嫌いです。他のコードを台無しにする可能性があります。
find Downloads -type f | while read file; do printf "%q\n" "$file"; done
まあ、私はあまりにも多くの複雑な答えを見ています。 findには「exec」オプションがあるため、findユーティリティの出力を渡したりループを作成したりしたくありません。
私の問題は、dbf拡張子を持つすべてのファイルを現在のフォルダーに移動したいということで、それらの一部には空白が含まれていました。
私はそれに取り組みました:
find . -name \*.dbf -print0 -exec mv '{}' . ';'
私にとってはとてもシンプルに見える
特定のフォルダからいくつかのディレクトリまたはファイルを順番に圧縮するために、同じ概念が必要でした。私は、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!!!"
どう思いますか?