「find」コマンドのコンテキストで「{} \;」はどういう意味ですか?

「find」コマンドのコンテキストで「{} \;」はどういう意味ですか?

というファイルを削除したいのですが、size = 0次のことを試しました:

find ./ -size 0 | xargs rm

しかし、名前がスペースで始まるファイルの場合は問題が発生します。

インターネットで検索したら、これを見つけました:

find ./ -size 0 -exec rm -i {} \;

動作します。ただし、私の使用方法は、これには洗練されすぎていると思いますxargs

どういう{} \;意味?

誰か説明してくれませんか?

私の英語はあまり上手ではないので、簡単な文章でお願いします。

答え1

{}は にとって全く意味をなさないのでbash、ここでは 実行されるコマンドへの引数として変更されずに渡されますfind

一方、;は に対して特別な意味を持ちますbash。これは通常、連続するコマンドが同じコマンド ラインにある場合に、それらのコマンドを区切るために使用されます。ここで、 のバックスラッシュは、\;によってセミコロンがコマンド区切り文字として解釈されるのを防ぎbash、基礎となるコマンド にパラメータとして渡すことができるようにするために正確に使用されていますfind。セミコロンを引用符で囲む (つまり";"または';') ことも、セミコロンを処理されないままにしておくための別の方法であった可能性があります。

コマンド:

find ./ -size 0 -exec rm -i {} \;

は、現在のディレクトリ(/ここでは は役に立たず、.ディレクトリ以外にはあり得ないことに注意してください)でサイズが 0 であるものを検索し、見つかったオブジェクトごとにコマンド を実行しますrm -i name。つまり、各ファイルを削除する場合は対話的にプロンプ​​トを表示します。 は、{}実行されたコマンドで見つかった各ファイル名に置き換えられます。 1 つの優れた機能は、ファイル名が何であれ(埋め込まれたスペース、タブ、改行、その他の文字が含まれていても)、このファイル名が厳密に 1 つの引数であることです。 の場合xargs、移植性のないハックが使用されない限り、これは当てはまりません。 は、節;を終了するためにあります。 の終わりを区切る必要があるのは、他のオプションが 1 つのオプションの後に続く可能性がある-execためですが、めったに行われません。例:find-exec

find . -name "*.js" -exec ls -l {} \; -name "special*" -exec wc -l {} \;

このコマンドの問題点は、プレーンでないファイルを無視しないため、ソケット、ブロックおよび文字デバイス、パイプ、およびディレクトリを削除するようにユーザーに求める可能性があることです。後者の場合、yes と答えても常に失敗します。

もう 1 つの問題は、ここではそれほど重要ではありませんが、rmサイズが 0 の各ファイルに対して が呼び出されることです。-exec末尾を から に/;置き換えると+、は、可能な限り最小限の回数 (多くの場合は 1 回だけ)findのみを呼び出すことで、サブプロセスの作成を最適化します。rm

このコマンドを変更する方法は次のとおりです。

find . -type f -size 0 -exec rm -i {} +

答え2

を使用するとfind -exec{}見つかった結果ごとに が展開されます。

たとえば、 、 の 3 つのファイルを含むディレクトリがある場合、examplea.txtb.txtのように展開されます。c.txtfind example/ -exec rm -i {} \;

find example/ -exec rm -i example/a.txt \;
find example/ -exec rm -i example/b.txt \;
find example/ -exec rm -i example/c.txt \;

\;末尾の は、exec パターンの終了を示すために単純にエスケープされています。;そうでない場合は、シェル自体によって解釈されます。

答え3

findコマンドのオプションと組み合わせてexec{}部分はコマンドの実行時に見つかったファイルの名前に置き換えられます。 も\;重要です。これは、実行されるコマンドの終了を定義するものだからです。

例えば

find ~ -name \*.bak -exec -rm -f {} \;

.bakユーザーのホーム ディレクトリ内またはその中に含まれるフォルダー内の、どこででもで終わるすべてのファイルを削除します。rm -f見つかった各ファイルに対して実行します。

xargs通常はパイプから標準入力行を受け取り、与えられたコマンドを実行したときにそこから引数の末尾部分を形成します。

答え4

これは古い質問ですが、もう少し情報を追加したいと思います。

find ./ -size 0 -exec rm -i {} \;

前のコマンドでは、 は\;エスケープされたセミコロンです。これにより、コマンドがシェルによって処理されなくなります (つまり、通常は が;コマンドを区切ります)。

引数-execは、エスケープされたセミコロンまでのすべてをコマンドとして解釈します\;(つまり、rm -i {}によって実行される内部コマンドになりますfind)。内部コマンド内では、{}パラメータ展開を表します。英語で言えば、「ここにあるファイル名を挿入する」という意味です。

したがって、見つかったファイルが「file-a.txt」と「file-b.txt」の場合、findが実行されます。rm -i file-a.txtrm -i file-b.txt

このコマンドの問題点は、プレーンでないファイルを無視しないため、ソケット、ブロックおよびキャラクタ デバイス、パイプ、ディレクトリを削除するようにユーザーに求める可能性があることです。後者の場合、yes と答えても常に失敗します (つまり、ディレクトリを再帰的に削除する必要があります)。

もう 1 つの問題は、ここではそれほど重要ではありませんが、rmサイズが 0 の各ファイルに対して が呼び出されることです。-exec末尾を から に/;置き換えると、 find は、可能な限り最小限の回数 (多くの場合は 1 回だけ)+のみを呼び出すことで、サブプロセスの作成を最適化します。rm

このコマンドを変更する方法は次のとおりです。

find ./ -type f -size 0 -exec rm -i {} +

curly bracketsまたはbraces:は{}さまざまな方法で使用できます

ブレース拡張

中括弧を使用してシーケンスを構築できます。

### prints out the numbers from 0 to 10
echo {0..10}

## prints out the same numbers, but in reverse order
echo {10..0}

## prints every second number, from 10 to 0
echo {10..0..2}

## prints every second letter, from z and working its way backwards to a.
echo {z..a..2}

また、2 つ以上のシーケンスを組み合わせることもできます。

## prints out a pair of letters, from aa to zz.
echo {a..z}{a..z}

接頭辞と接尾辞の追加:

### adds '"' as prefix and suffix
echo \"{These,words,are,quoted}\"
# output: "These" "words" "are" "quoted"

# concatenates the files file1, file2, and file3 into combined_file.
cat {file1,file2,file3} > combined_file

# copies "file22.txt" to "file22.backup"
cp file22.{txt,backup}

注記:

中括弧内にスペースを入れることはできません{...}。ただし、スペースが引用または逃げた

echo {file1,file2}\ :{\ A," B",' C'}
# output: file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C

拡張ブレース拡張。

()配列を構築するには中括弧を使用できます。Bash の配列は、次のように要素を括弧で囲み、各要素をスペースで区切ることで定義されます。

month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")

配列内の要素にアクセスするには、括弧内のインデックスを使用します[]

 # Array indexes start at [0], so [3] points to the fourth item
$ echo ${month[3]}
## output: Apr

したがって、次のような配列を作成できます。

## builds an array that contains all the 2-letter combinations of the entire alphabet.
letter_combos=({a..z}{a..z})

## contains all the binary numbers for an 8-bit register, in ascending order,
## from 00000000, 00000001, 00000010, etc., to 11111111. 
dec2bin=({0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1})

この最後のものは特に興味深いものです。なぜなら、dec2bin を使って 8 ビットの 10 進数から 2 進数へのコンバーターを構築できるからです。25 が 2 進数で何であるかを知りたいとします。次のようにします。

$ echo ${dec2bin[25]}
## output: 00011001

しかし、10 進数を 2 進数に変換するより良い方法はないのでしょうか?

  • はい、あります。でも、それでも面白いですよね?

コマンドのグループ化

{ ... }現在のシェルコンテキストで実行されるコマンドのリストを配置するために使用できます。サブシェル作成されます。リストの後のセミコロン;(または改行)は必須

括弧は()コマンドを実行するために使われますサブシェル:

menu_type=bar
echo $menu_type
## output: bar

## new lets called in a sub-shell
(menu_type=superbar; echo $menu_type)
## output: superbar

## back to the context
echo $menu_type
## output: bar

superbarの新しい値にアクセスできませんmenu_type

ただし、次のように実行すると:

{ menu_type=superbar; echo $menu_type; }
## output: superbar

echo $menu_type
## output: superbar

{ ... }サブシェルを作成しないので、menu_type値にアクセスできます。

{ ... }は とも呼ばれinline group、実質的には匿名関数 (つまり、名前のない関数) を作成します。簡単に言えば、「標準」関数とは異なり、 内の変数は{ ... }スクリプトの残りの部分から参照できます。

また、{ ... }を使用して、複数のコマンドの出力を にグループ化したりstdout、 へのリダイレクトを受け取ったりすることもできますstdin。 例を見てみましょう。

#!/bin/bash
# rpm-check.sh
#  Queries an rpm file for description, listing, and whether it can be installed.
#  Saves output to a file.

SUCCESS=0
E_NOARGS=65

if [ -z "$1" ]; then
  echo "Usage: `basename $0` rpm-file"
  exit $E_NOARGS
fi  

{ # Begin command group.
  echo
  echo "Archive Description:"
  rpm -qpi $1       # Query description.
  echo
  echo "Archive Listing:"
  rpm -qpl $1       # Query listing.
  echo
  rpm -i --test $1  # Query whether rpm file can be installed.
  if [ "$?" -eq $SUCCESS ]
  then
    echo "$1 can be installed."
  else
    echo "$1 cannot be installed."
  fi  
  echo              # End command group.
} > "$1.test"       # Redirects output of everything in block to file.

echo "Results of rpm test in file $1.test"

exit 0

ここで、グループの I/O リダイレクトを実行する方法を見てみましょうstdin

#!/bin/bash
File=/etc/fstab

## reads the first two lines of the file
{
  read line1
  read line2
} < $File

echo "First line in $File is:"
echo "$line1"
echo
echo "Second line in $File is:"
echo "$line2"

exit 0

コマンドグループの出力をファイルに保存する別の例

## exec commands sequentially and redirects the output of the ls command into the png-list.txt file
echo "I found all these png files:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls > png-list.txt

## exec commands sequentially and redirects the output of the group into the png-list.txt file
{ echo "I found all these png files:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls; } > png-list.txt

違いは何ですか、テオ?

まあ、若きパダワンよ。2 番目はpng-list.txt、行 から始まり“I found all these png files:“、コマンド出力で終わるすべての出力を含むファイルを作成しますls

サブシェルハック

{ ... }Bashは中括弧グループコマンドのサブシェルを作成しますもし、もし、これはパイプラインの一部です。たとえば、次のようになります。

$ { A=1; { A=2; sleep 2; } ; echo $A; }
## output: 2

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
## output: 1

注記:

中括弧とその中にあるコマンド リストの間にはスペースがあります。これは、{と が}予約語 (つまり、シェルに組み込まれているコマンド) であるためです。また、コマンド リストはセミコロンで終わる;か、コマンドを区切るために改行を使用する必要があります。

パラメータ拡張

さて、話を戻しましょう

month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
echo ${month[3]}
## output: Apr

ここで、中括弧は{}シーケンス ビルダーの一部として使用されているのではなく、パラメーター展開を生成する方法として使用されています。パラメーター展開には、ボックスに記載されている内容が含まれます。

中括弧内の変数または式を取得し、それが表すものに展開します。

これはどういう意味ですか、テオ?

つまり、${...}シェルにその内部にあるものを展開するように指示するということです。この場合、monthは先ほど定義した配列です。

month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")

3また、配列内の項目は を指します"Apr"(つまり、Bash の配列の最初のインデックスは です[0])。つまりecho ${month[3]}、 は展開後、 echo に変換されます"Apr"

変数をその値として解釈することは、変数を拡張する 1 つの方法ですが、活用できる方法は他にもいくつかあります。パラメータ拡張を使用して、変数から読み取った内容を操作できます (つまり、末尾の一部を切り取るなど)。

次のような変数があるとします。

a="This sentence is too longgg"

## chops off the last two gs
echo ${a%gg}
## output: This sentence is too long

これは、ファイルをある形式から別の形式に変換する場合に便利です。たとえば、 という JPEG 画像を受け取りimage.jpg、 という PNG 画像に変換するコマンドがあるとしますimage.png

convert image.jpg image.png

次のように書き直すことができます。

i='image.jpg'
## chops off the extension 'jpg' and adds 'png'
convert $i "${i%jpg}png"
## output: convert image.jpg image.png

しかし、ファイル名を単に書き込むよりも、これがどのように役立つのでしょうか?

何百もの JPEG 画像を含むディレクトリがある場合、それを PNG に変換する必要があります。そのディレクトリで次のコマンドを実行するだけです。

for i in *.jpg; do convert $i ${i%jpg}png; done

…そしてすべての写真は自動的に変換されます。若きパダワンよ、どういたしまして。

変数の先頭からチャンクを切り取る必要がある場合は、 の代わりに%を使用します#

$ a="Hello World!"

## chops off the word 'Hello' and adds 'Goodbye'
$ echo Goodbye${a#Hello}
## output: Goodbye World!

テキストのプレースホルダー

(つまり、文字列の置換オプションの後にxargs -i)使用されます。{}二重中括弧は出力テキストのプレースホルダーです。

## Execute 'echo ./<file>' for each file in the directory
ls . | xargs -i -t echo ./{} $1
#            ^^         ^^

パス名

パス名は、完全なパスを含むファイル名です。たとえば、 です/home/<user>/Notes/todo.txt。これは絶対パスと呼ばれることもあります。を含む構文{}でよく見かけますが、これはシェルの組み込みではありません。を含む場合、find は を選択したファイルの完全なパス名に置き換えます。find-exec <command> \;<command>{}"{}"

# Removes all core dump files from user's home directory.
find ~/ -name 'core*' -exec rm {} \;

関連情報