BASHスクリプトを `for`にスペース付きのファイル名を処理させる(または回避策)


12

BASHを数年間使用していますが、BASHスクリプトの経験は比較的限られています。

私のコードは以下の通りです。現在のディレクトリ内からディレクトリ構造全体を取得し、に複製する必要があり$OUTDIRます。

for DIR in `find . -type d -printf "\"%P\"\040"`
do
  echo mkdir -p \"${OUTPATH}${DIR}\"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done

問題は、ここに私のファイル構造のサンプルがあります:

$ ls
Expect The Impossible-Stellar Kart
Five Iron Frenzy - Cheeses...
Five Score and Seven Years Ago-Relient K
Hello-After Edmund
I Will Go-Starfield
Learning to Breathe-Switchfoot
MMHMM-Relient K

スペースに注意してください:-Sそしてfor、単語ごとにパラメータを取るため、スクリプトの出力は次のようになります。

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Learning"
Created Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot"
Created Breathe-Switchfoot

しかし、の出力からファイル名全体(一度に1行)を取得する必要がありますfind。またfind、各ファイル名を二重引用符で囲んでみました。しかし、これは役に立ちません。

for DIR in `find . -type d -printf "\"%P\"\040"`

そして、この変更された行での出力:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"""
Created ""
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"Learning"
Created "Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot""
Created Breathe-Switchfoot"

ここgstreamerで、次のような類似した構造の各ファイルに関係するより複雑なコマンドを実行したいので、このように繰り返すことができる何らかの方法が必要です。どうすればいいですか?

編集:ディレクトリ/ファイル/ループごとに複数行のコードを実行できるようにするコード構造が必要です。不明な場合はごめんなさい。

解決策:最初に試しました:

find . -type d | while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done

これはほとんどの部分でうまくいきました。しかし、後でパイプがサブシェルで実行されているwhileループになるため、ループに設定された変数は後で利用できず、エラーカウンターの実装が非常に困難になることがわかりました。私の最終的な解決策(SOに関するこの回答から):

while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done < <(find . -type d)

これにより、後でスクリプト内で使用可能なままになるループ内の変数を条件付きでインクリメントできました。


Why_would_you_ever_need_a_space_in_a_file_name?
ケビンパンコ14

本当、私の好みではありません。
ただし

1
実際、ファイル名にはスペースを含める必要があります。/印刷できない文字以外は許可します。ただし/\0それ以外は許可されているため、許可する必要があります。
ケビンパンコ14年

回答:


11

パイプをループにfind入れる必要がありwhileます。

find ... | while read -r dir
do
    something with "$dir"
done

また、-printfこの場合は使用する必要はありません。

必要に応じて、ヌルバイト区切り文字(* nixファイルパスに表示できない唯一の文字)を使用して、名前に改行を含むファイルに対してこの証明を行うことができます。

find ... -print0 | while read -d '' -r dir
do
    something with "$dir"
done

また$()、バックティックの代わりに使用すると、より汎用性が高く簡単になります。ネストをより簡単に行うことができ、引用をより簡単に行うことができます。この考案された例はこれらのポイントを説明します:

echo "$(echo "$(echo "hello")")"

バックティックでそれを試してみてください。


2
また、ではなく"$dir"を使用することをお勧めします"${dir}"-$ {dir} nameと$ {dirname}の違いは簡単にわかりますが、$ dirnameはどちらの方法でも解釈できます。
ジェームズポーリー

ここで重要なことは、read行全体をに読み込む${dir}ことです。したがって、IFSは重要ではありません。
ジェームズポーリー

1
。何も変数名の後がない場合は$ /」タイプミスを見つけるためのおかげで、中括弧は必要ありません。
一時停止追って通知があるまで。

4
これは、スペースを含むパス名(U + 0020)を処理しますが、改行(U + 000A)を含むパス名を適切に処理できません。私が好むのfind … -print0 | xargs -0 …は、それが使用する区切り文字がPOSIXパスアナムで許可されていない唯一の文字であるNUL(U + 0000)に正確に対応するためです。
クリスジョンセン

2
パーフェクト!まさに私が探していたもの。あなたがパイプできるかもしれないということは私には決して起こりませんでしたwhile。@Chris Johnsen:確かに、音楽をリッピングするプログラムでさえ、ファイル名に改行を入れる傾向はありません。そして、彼らがしなければ、私は(すなわち:何かがうまくいかない)を知りたい...とすぐにそれらを取り除く
サミュエルJaeschke

8

スペースを含むファイル名を処理するスクリプトの例については、数日前に書いたこの回答を参照してください。

あなたがやろうとしていることを達成するために、もう少し複雑な(しかしより簡潔な)方法があります:

find . -type d -print0 | xargs -0 -I {} mkdir -p ../theredir/{}

-print0引数をnullで区切るようにfindに指示します。xargsの-0は、ヌルで区切られた引数を期待するように指示します。これは、スペースをうまく処理することを意味します。

-I {}文字列{}をファイル名で置き換えるようにxargsに指示します。これは、コマンドラインごとに1つのファイル名のみを使用する必要があることも意味します(通常、xargsはその行に収まるだけの数を詰め込みます)

残りは明白なはずです。


しかし、デニスウィリアムソンの提案は(タイプミスは別として)はるかに読みやすく、したがってほぼすべての点で望ましいものです。
ジェームズポーリー

mkdirの場合は動作しますが、申し訳ありませんが、もっと明確にすべきでした-各ファイルに対して一連のコマンドを実行したいと思います。後で、同様のルーチンで、入力ファイル名に基づいて出力ファイル名を生成し(拡張子.oggを削除して.mp3を追加する)、gst-launchを呼び出すときにこれらの複数の変数をピップラインで使用したいと思います。
サミュエルジェシュケ2009

5

あなたが直面している問題は、forステートメントが個別の引数として検索に応答していることです。スペース区切り文字。スペースで分割しないようにするには、bashのIFS変数を使用する必要があります。

これを行う方法を説明するリンクを次に示します。

IFS内部変数

この問題を回避する1つの方法は、デフォルトの空白(スペース、タブ、改行)以外(この場合はコンマ)でフィールドを分割するように、Bashの内部IFS(内部フィールドセパレーター)変数を変更することです。

#!/bin/bash
IFS=$';'

for I in `find -type d -printf \"%P\"\;`
do
   echo "== $I =="
done

%Pの後にフィールド区切り文字を出力するように検索を設定し、IFSを適切に設定します。ファイル名に含まれる可能性が非常に低いため、セミコロンを選択しました。

もう1つの方法は-exec、forループを完全にスキップすることで、検索から直接mkdirを呼び出すことです。追加の解析を行う必要がない場合です。


ファイル名にIFSが含まれている場合はどうなりますか?次に、別のものを選択する必要があります。しかし、その後、もしも…
さらに通知があるまで一時停止します。

3
/POSIXおよび:DOSファイルシステムで選択できます。IFS用に選択できるさまざまなファイルシステムの違法文字があります。より複雑なものはすべて、perlを使用したほうがよいでしょう。
ダレンホール

2
/を使用する際の問題は、ディレクトリ区切り文字でありfind、スラッシュを含むパスを持つファイル名を返すことです。スクリプトのセミコロンをスラッシュに変更してみてください。エコーはディレクトリとファイル名を別々の行に出力します。
追って通知があるまで一時停止します。

これも非常に便利です。私はwhileオプションへのパイプで行ってきましたが、これも非常に実行可能です。はい、後で同様の構造で、さらに解析を行う必要がありました。(入力ファイル名は.ogg filesrcで、gstパイプラインのように渡されますが、出力ディレクトリに基づいて.mp3で終わる同等のものが生成され、パイプラインにとして渡されfilesinkます。もちろん、これを行う必要がありますファイルごとに、一部echoはユーザーに提供されます。)
サミュエルジェシュケ

4

ループの本文が複数のコマンドである場合、xargsを使用してシェルスクリプトを駆動することができます。

export OUTPATH=/some/where/else/
find . -type d -print0 | xargs -0 bash -c 'for DIR in "$@"; do
  printf "mkdir -p %q\\n" "${OUTPATH}${DIR}"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done' -

シェルがBourne / POSIXの種類の場合(シェルスクリプトで$ 0を設定するために使用されます)、末尾のダッシュ(または他の「単語」)を必ず含めてください。また、シェルスクリプトは直接プロンプトに表示されるのではなく、引用符で囲まれた文字列内に書き込まれるため、引用には注意が必要です。


別の興味深い概念。ありがとう-私は後でこれを使用できると確信しています:)
サミュエルジェシュケ

1

あなたが持っているあなたの更新された質問に

mkdir -p \"${OUTPATH}${DIR}\"

これは

mkdir -p "${OUTPATH}${DIR}"

ありがとう。修繕。また、代わりにDIRのファイル名に読んでいた-コピー&ペースト:P
サミュエルJaeschke

1
find . -type d -exec mkdir -p "{}\040" ';' -exec echo "Created {}\040" ';'

0

または、全体の複雑さを大幅に軽減します。

% rsync -av --include='*/' --exclude='*' SRC DST

これにより、SRCのディレクトリ構造がDSTに複製されます。


いいえ、そのような反復構造が必要です。これにより、各ファイルに対して複数行のコードを実行できます。「今、私はこのように反復できる何らかの方法が必要です。なぜなら、次の同様の構造で各ファイルに対してgstreamerを含むより複雑なコマンドを実行したいからです。」不明な場合はごめんなさい。
サミュエルジェシュケ

私が与えたコマンドは、あなたが求めた問題を解決します。これがあなたの側のより大きな「パイプライン」の一部であるかどうかは関係ありません。質問で説明されているように、rsync-approachが機能する他の誰かが問題を抱えています。潜在的なunclearity :)について後悔することにするので、必要はありません
アキラ

うん。いいえ、後で同様のwhile... do... done構造を使用してfindから同様の処理を行うことを意味します。各ファイルで複数行のコードを実行する必要があります(文字列、エコー、gst-launchなどを変更します) )rsyncこれを達成しません。そのため、同様の構造内でより複雑な一連のコマンドを実行できるようにする必要があると指定しました。私のスクリプトはこのループ構造を2回使用しているので、質問については、真ん中に残骸が少ないものを投稿しました。
サミュエルジェシュケ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.