サブシェルで dirname を使用すると問題が発生します。

サブシェルで dirname を使用すると問題が発生します。

私はサブディレクトリを介して繰り返す必要がある(1行)スクリプトを書いています。ハイパーリンクを含む.txtファイルを見つけます。 wgetを使用してコンテンツをインポートし、テキストファイルと同じディレクトリにダウンロードします。

見つかったすべてのテキストファイルには有効なハイパーリンクのみが含まれていると想定されます。

これをテストするには:
サブディレクトリを作成します。内容を含む./s1
テキストファイルを作成します。 ./s1/s1.txt
./s1/s1.txtwww.google.com

次の行は次のとおりです。

find . -type f -name "*.txt" -exec bash -cx "wget -i \"{}\" -P  $(dirname \"{}\") " \;

問題は$(dirname \"{}\")正しく拡張されないことです。実行されるbashコマンドは次のとおりです。

+ wget -i ./s1/s1.txt -P .

したがって、$(dirname \"{}\")返される.
効果は新しいものです。目次 ./s1/s1.txt建設される。したがって、ダウンロードしたファイルは次のように保存されます。./s1/s1.txt/index.html

私が交換した場合、$(dirname \"{}\")出力$(echo \"{}\")は次のようになります。

+ wget -i ./s1/s1.txt -P ./s1/s1.txt

したがって、パラメータ渡し自体は正確です。したがって、結果がdirname呼び出しbashシェルに正しく返されないと仮定します。またはdirnameまったく評価しないでください。

ちょうどbashコマンドを実行したとき

bash -cx "wget -i ./s1/s1.txt -P  $(dirname ./s1/s1.txt)" 

(したがって、findコマンドの外部) コマンドは期待どおりに実行されます。

+ wget -i ./s1/s1.txt -P ./s1

このラインを操作する正しい方法は何ですか?

答え1

ここでは、次のことができます。

find . -name '*.txt' -type f -execdir wget -i {} -P . ';'

見つかったファイルのディレクトリでコマンドを実行するのではなく、非標準ですが、非常に一般的な-execdir述語を使用してください(そして、GNUを含むいくつかの実装が前に来ることができるフルパスではなくファイル名に拡張されます)。find-exec{}./findfind

GNUを使用すると、find一部xargsを並列に実行できます。

xargs -r0 -n4 -P10 -a <(
  find . -name '*.txt' -type f -printf '-i\0%p\0-P\0%h\0'
  ) wget

find引数リストを作成し、wgetそれをNULで区切って出力します(0は、ファイルパスの外部コマンドライン引数には現れない唯一のバイト値です)。xargs一度に最大パラレルでインスタンスを実行します。4wget10P

存在するzsh

for file (**/*.txt(N.)) wget -i $file -P $file:h

(次に追加D グローバル予選findメソッドのように隠されたファイルも処理したい場合)。


あなたの

find . -type f -name "*.txt" -exec bash -cx "wget -i \"{}\" -P  $(dirname \"{}\") " \;

は二重引用符内にあるため、コマンドを入力したシェルはコマンドを渡す前に$(...)出力に展開します。dirname \"{}\"find

dirname \"{}\"、sh / bashのdirname '"{}"'出力(現在の作業ディレクトリパス)と同じです。dirname anything-that-does-not-contain-a-slash-and-does-not-start-with-dash.

したがって、 find は次の引数で呼び出されます。

  1. find
  2. .
  3. -type
  4. f
  5. -name
  6. *.txt
  7. -exec
  8. bash
  9. -cx
  10. wget -i "{}" -P .
  11. ;

find次のパラメータで実行されますbash

  1. bash
  2. -cx
  3. wget -i "./path/to/the/file.txt" -P .

見つかった各ファイルに対して、bashは次のことを実行しますwget

  1. wget
  2. -i
  3. ./path/to/the/file.txt
  4. -P
  5. .

しかし、もしファイルパス\含めると、潜在的に災害になる可能性がある、、、または"文字`は含まれません"(たとえば、名前がファイルの場合$(rm -rf ~).txt)。

二重引用符の代わりに一重引用符を使用する場合:

find . -type f -name "*.txt" -exec bash -cx 'wget -i "{}" -P  "$(dirname "{}")"' \;

修正された可能性がありますが、上記の理由により、まだ非常に間違った状態です。{}しなければならないいいえコードで計算されたパラメータに組み込まれます。バラより@gilsの答えこれを正しく行う方法。


1 -execdirAFAIK OpenBSDでは、1996年のFreeBSD、1997年のFreeBSD、2002年のNetBSD、find2005年のGNU、2010年のsfind、2014年に少なくともtoyboxに追加されました。

答え2

コメントで述べたように、findこのbash部分でプレースホルダを使用しないでください。{}これは信頼できず、可能です。セキュリティ問題(シェル注入)

この方法を使用する方が良いです。

 find . -type f -name '*.txt' -exec sh -c '
     for file; do
         wget -i "$file" -P "$(dirname "$file")"
     done
 ' sh {} +

または標準を使用してくださいパラメータ拡張(より効率的なものに加えて、ディレクトリ名が改行文字で終わってもまだ機能するという利点があります):

 find . -type f -name '*.txt' -exec sh -c '
     for file; do
         wget -i "$file" -P "${file%/*}"
     done
 ' sh {} +

$ tree
.
└── s1
    ├── index.html
    └── s1.txt

1 directory, 2 files

関連情報