web-dev-qa-db-ja.com

bashループリストの空白をエスケープするにはどうすればよいですか?

特定のディレクトリのすべての子ディレクトリ(ただし、ファイルではない)をループする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"は次の出力を生成します。

チェリー
ヒル
新規
ヨーク
市
117
MCS

まず、そのようにしないでください。最適なアプローチは、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
103
Charles Duffy
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
21
cbliard

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

7

次を使用して、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
4
amazingthere

なぜ置くだけではない

IFS='\n'

forコマンドの前に?これにより、フィールド区切り文字が<スペース> <タブ> <改行>から<改行>に変更されます

4
oshunluvr

私が使う

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 からのアイデア

4
murpel

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

#!/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
4
Gordon Davisson
find . -print0|while read -d $'\0' file; do echo "$file"; done
4
Freakus

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

read artist;

find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 '{}' \;
3
hardbutnot

Jonathan の説明に追加するには、次のようにfindとともにxargs-print0オプションを使用します。

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

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

2
Adam Rosenfield

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

function recursedir {
    local item
    for item in "${1%/}"/*
    do
        if [ -d "$item" ]
        then
            recursedir "$item"
        else
            command
        fi
    done
}
1
Florian Bender
#!/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!

乾杯!

1
Sony George

ファイルリストを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の設定も嫌いです。他のコードを台無しにする可能性があります。

1
Steve Zobell

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

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

順番に印刷する

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

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

0
Jeffrey04
find Downloads -type f | while read file; do printf "%q\n" "$file"; done
0
Johan Kasselman

まあ、私はあまりにも多くの複雑な答えを見ています。 findには「exec」オプションがあるため、findユーティリティの出力を渡したりループを作成したりしたくありません。

私の問題は、dbf拡張子を持つすべてのファイルを現在のフォルダーに移動したいということで、それらの一部には空白が含まれていました。

私はそれに取り組みました:

 find . -name \*.dbf -print0 -exec mv '{}'  . ';'

私にとってはとてもシンプルに見える

0
Tebe

特定のフォルダからいくつかのディレクトリまたはファイルを順番に圧縮するために、同じ概念が必要でした。私は、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!!!"

どう思いますか?

0
Hìr0