マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力1行で必要な処理を終わらすことをシェル芸と呼びます。
LinuxはGUIの操作も行えますが、ターミナルを開きコマンドで処理を行い管理する事が非常に多いです。この記事では、簡単なワンライナー(1行処理)から解説を行い、それらを組み合わせて高度な処理を行う方法を解説します。
はじめに、標準入力・標準出力・標準エラー出力を解説し、パイプ、プロセス置換、コマンド置換、リダイレクト、ブレース展開、三項演算子、グルーピング、Glob展開、エスケープシーケンス、変数展開、算術式展開について簡単に解説します。これらの機能とコマンドを組み合わせる事で、1行コマンド(ワンライナー)で高度な処理を行える様になります。
標準入力・標準出力・標準エラー出力
標準入力(Standard Input)、標準出力(Standard Output)、および標準エラー出力(Standard Error)は、Unix/Linuxシステムにおいてプロセス間のデータの受け渡しや情報の表示に使用される重要なコンセプトです。
- 標準入力(Standard Input): 標準入力は、プログラムがデータを受け取るためのチャネルです。通常、キーボードからの入力が標準入力として使用されますが、リダイレクションやパイプを使用して他のソースからデータを受け取ることもできます。標準入力は、プログラム内で特定のファイルディスクリプタ(File Descriptor)で表され、通常は0という数値が割り当てられます。
- 標準出力(Standard Output): 標準出力は、プログラムが結果や出力データを表示するためのチャネルです。通常、結果やメッセージはコンソールに表示されますが、リダイレクションやパイプを使用して他の場所に出力することもできます。標準出力も、特定のファイルディスクリプタで表され、通常は1という数値が割り当てられます。
- 標準エラー出力(Standard Error): 標準エラー出力は、プログラムがエラーメッセージやエラー関連の情報を表示するためのチャネルです。エラーメッセージは通常、標準出力とは別の場所に表示されます。標準エラー出力も、特定のファイルディスクリプタで表され、通常は2という数値が割り当てられます。
- /proc/$pid/fd/:
/proc/$pid/fd/
ディレクトリは、Linuxシステム上で実行中のプロセスのファイルディスクリプタの情報を提供する仮想ファイルシステムです。ここで$pid
はプロセスのIDです。このディレクトリ内のファイルは、プロセスが現在開いているファイルやデバイスに対応しており、ディレクトリ内の各ファイルはファイルディスクリプタにリンクされています。例えば、/proc/$pid/fd/0
はプロセスの標準入力を表し、/proc/$pid/fd/1
は標準出力、/proc/$pid/fd/2
は標準エラー出力を表します。これにより、プロセスがどのファイルやデバイスにデータを読み書きしているかを確認することができます。
例えば、プロセスIDが1234のプロセスのファイルディスクリプタを確認するには、/proc/1234/fd/
ディレクトリにアクセスします。ディレクトリ内の各ファイルは、実際のファイルやデバイスに対するシンボリックリンクとして表されます。これにより、プロセスがどのファイルディスクリプタを使用しているかを確認することができます。
readlink /proc/1234/fd/1
このコマンドは、/proc/1234/fd/1
にリンクされている実際のファイルパスを表示します。
標準入力、標準出力、および標準エラー出力は、プログラムやシェルスクリプトを実行する際に非常に重要な役割を果たします。これらのチャネルを適切に制御し、データの受け渡しや情報の表示を管理することで、プロセス間の通信やエラーハンドリングを効果的に行うことができます。
パイプ(Pipe)
パイプ(Pipe)は、Linuxシェルで複数のコマンドを連結してデータの流れをパイプラインとして扱うための機能です。パイプを使用することで、1つのコマンドの出力を別のコマンドの入力として簡単に渡すことができます。
パイプは|記号を使用して表現されます。以下にパイプの詳細な解説と使用例を示します。
- パイプは、1つのコマンドの出力を別のコマンドの入力にリダイレクトします。
- コマンド1の出力をコマンド2の入力に渡す場合、次のように記述します:command1 | command2
- コマンド1の実行結果は、一時的なファイルではなく、メモリ上のバッファ(パイプ)に格納されます。
- コマンド1の出力がパイプに格納され、コマンド2はそのパイプからデータを読み取ります。
- この方法により、大量のデータを一時的なファイルに保存せずに効率的に処理できます。
# ファイルの内容を表示してからソートする
cat file.txt | sort
# プロセスリストを表示してから特定のプロセスをフィルタリングする
ps aux | grep "nginx"
# ディレクトリ内のファイル数をカウントする
ls -l | wc -l
上記の例では、cat
コマンドの出力をsort
コマンドに渡し、ファイルの内容をソートして表示します。また、ps aux
コマンドの出力をgrep
コマンドに渡して、特定のプロセスをフィルタリングします。さらに、ls -l
コマンドの出力をwc -l
コマンドに渡して、ディレクトリ内のファイル数をカウントします。
パイプは、複数のコマンドを連結してデータを効率的に処理するための強力なツールです。シェル芸にとって必須の機能です。
プロセス置換(Process Substitution)
プロセス置換は、一時的なファイルを作成することなく、コマンドの出力をファイルのように扱う方法です。プロセス置換は、<()
または()>
という構文を使用します。プロセス置換は、通常、コマンドの引数として使用されます。
例えば、次のコマンドは、ファイル1とファイル2の差異を表示します。
diff <(cat file1) <(cat file2)
<(cat file1)と<(cat file2)がプロセス置換です。
コマンド置換(Command Substitution)
コマンド置換は、コマンドの出力を取得し、その出力を別のコマンドの引数として使用する方法です。コマンド置換は、バッククォート()または
$()という構文を使用します。
例えば、次のコマンドは、現在のディレクトリに存在するファイルの数を表示します。
echo "ファイルの数: $(ls | wc -l)"
$(ls | wc -l)がコマンド置換です。
リダイレクト(Redirection)
- リダイレクトは、コマンドの入力や出力をファイルやデバイスに向ける方法です。一般的なリダイレクトの操作には、次のようなものがあります。
>
: 出力をファイルに書き込む(ファイルが存在する場合は上書きされる)。>>
: 出力をファイルに追加する。<
: ファイルから入力を受け取る。2>
: エラーメッセージをファイルに書き込む。2>>
: エラーメッセージをファイルに追加する。
例えば、次のコマンドは、コマンドの出力をファイルに書き込みます。
ls > file.txt
ブレース展開(Brace Expansion)
ブレース展開は、波括弧({})を使用して、複数の項目を生成する方法です。ブレース内にはカンマで区切られた項目のリストを指定します。
例えば、次のコマンドは、ファイル1.txtからファイル3.txtまでの範囲のファイルをコピーします。
cp file{1..3}.txt destination/
この例では、file1.txt
、file2.txt
、file3.txt
の3つのファイルがdestination/
ディレクトリにコピーされます。
また、ブレース展開は文字列の組み合わせにも使用できます。例えば、次のコマンドは、prefix
とsuffix
の組み合わせを使用してファイルを作成します。
touch {prefix1,prefix2,prefix3}_{suffix1,suffix2}.txt
prefix1_suffix1.txt
、prefix1_suffix2.txt
、prefix2_suffix1.txt
、prefix2_suffix2.txt
、prefix3_suffix1.txt
、prefix3_suffix2.txt
の6つのファイルが作成されます。
三項演算子(Ternary Operator)
三項演算子は、条件に基づいて値を選択するための演算子です。一般的な構文は以下のようになります。
条件式 ? trueの場合の値 : falseの場合の値
例えば、次のコマンドは、変数x
の値が10以上かどうかに基づいてメッセージを表示します。
echo "xは$(($x >= 10 ? "10以上" : "10未満"))です。"
この例では、$x
が10以上の場合は”10以上”と表示され、10未満の場合は”10未満”と表示されます。
グルーピング(Grouping)
グルーピングは、複数のコマンドを1つのグループにまとめる方法です。グループ化は、()を使用して行われます。
例えば、次のコマンドは、2つのコマンドをグループ化し、その出力をファイルに書き込みます。
(echo "Hello"; echo "World") > output.txt
Glob展開(Glob Expansion)
Glob展開は、ワイルドカード文字(*や?など)を使用してパターンに一致するファイル名を展開する方法です。Glob展開は、シェルがファイル名の展開を行うために使用されます。
例えば、次のコマンドは、*.
txtというパターンに一致するすべてのテキストファイルのリストを表示します。
ls *.txt
エスケープシーケンス(Escape Sequence)
エスケープシーケンスは、バックスラッシュ(\)と特定の文字の組み合わせで構成される特殊な文字列です。エスケープシーケンスは、特定の制御文字や特殊文字を表現するために使用されます。
例えば、\nは改行文字を表し、\t
はタブ文字を表します。また、\\はバックスラッシュそのものを表します。
エスケープシーケンスは、文字列内の特定の文字をエスケープしたり、特殊な文字を挿入したりするために使用されます。例えば、次のコマンドは、エスケープシーケンスを使用してダブルクォートをエスケープします。
echo "He said, \"Hello!\""
変数展開(Variable Expansion)
変数展開は、シェル変数の値を取得したり、他の文字列と組み合わせたりするために使用される機能です。変数は$
で始まる名前で表されます。
例えば、次のコマンドは、変数name
の値を表示します。
echo $name
変数展開はさまざまな形式で使用でき、例えば${}を使用して変数名を囲むことで、変数名の一部を明示的に指定することもできます。また、${}内でデフォルト値や演算を行うことも可能です。
変数展開の書き方と処理内容
パラメータ展開 | 機能 |
---|---|
${#変数} | 変数の文字数を取得する |
${変数:-文字列} | 変数がNULLの場合、指定した文字列を返す |
${変数:+文字列} | 変数がNULL以外の場合、指定した文字列を返す |
${変数:=文字列} | 変数がNULLの場合、変数に指定した文字列を代入する |
${変数:?文字列} | 変数がNULLの場合、指定した文字列を返しエラー終了する |
${変数#文字列} | 変数より、前方から最短一致した箇所を除外する(基本ワイルドカード組み合わせ要) |
${変数##文字列} | 変数より、前方から最長一致した箇所を除外する(基本ワイルドカード組み合わせ要) |
${変数%文字列} | 変数より、後方から最短一致した箇所を除外する(基本ワイルドカード組み合わせ要) |
${変数%%文字列} | 変数より、後方から最長一致した箇所を除外する(基本ワイルドカード組み合わせ要) |
${変数:N} | 変数より、N文字以降の文字のみ抽出する |
${変数:N:X} | 変数より、N文字目からX個の文字を抽出する |
${変数: -N} | 変数より、後ろからN文字のみ抽出する |
${変数: -N:X} | 変数より、後ろからN文字、X個の文字のみ抽出する |
${変数::N} | 変数より、頭からN文字のみ抽出する |
${変数::-N} | 変数より、後ろからN文字除外する |
${変数/置換前/置換後} | 変数の内容を、置換前文字列を置換後文字列に変換して返す |
${変数^[文字列]} | 変数の一文字目を大文字にする(文字列を指定してる場合はその文字列のみを対象にする) |
${変数^^[文字列]} | 変数全体を大文字にする(文字列を指定してる場合はその文字列のみを対象にする) |
${変数,[文字列]} | 変数の一文字目を小文字にする(文字列を指定してる場合はその文字列のみを対象にする) |
${変数,,[文字列]} | 変数全体を小文字にする(文字列を指定してる場合はその文字列のみを対象にする) |
${!文字列*} | 指定された文字列から始まる変数を取得する |
算術式展開(Arithmetic Expansion)
算術式展開は、数値計算を行うために使用される機能です。算術式展開は、$(( ))の形式で行われます。
例えば、次のコマンドは、2つの数値を足し算します。
echo $((5 + 3))
算術式展開では、四則演算や比較演算、ビット演算などさまざまな演算がサポートされています。また、変数や関数の値を参照することも可能です。
実用的なワンライナーの紹介
基本の解説だけでかなりのボリュームだったので、お腹いっぱいかもしれませんが、ここから実際に上で解説した基本を活用し、ワンライナーの実用例を紹介します。是非一緒にコマンド操作を行なってください。
ファイルの行数をカウントする
wc -l < filename
wc
コマンドを使用して指定したファイルの行数をカウントしています。-l
オプションは、行数だけを出力するようにします。<
演算子は、ファイルを入力ストリームとして渡すために使用されます。
ファイル内の単語数をカウント
wc -w < $(find . -name "*.txt")
f
indコマンドを使用してカレントディレクトリ以下のすべての.txtファイルを検索し、プロセス置換を使用してファイルをwcコマンドに渡して単語数をカウントします。
ファイルの内容を逆順に表示する
tac filename
tac
コマンドは、ファイルの内容を逆順に表示します。ファイルの各行は、末尾から先頭に向かって表示されます。
ファイルから重複した行を削除する
awk '!seen[$0]++' filename
awk
コマンドを使用してファイルから重複した行を削除します。!seen[$0]++は、seenという配列に現れた行を記録し、重複している場合はそれを無視するという意味です。
CSV ファイルの特定の列を抽出する
cut -d ',' -f 2 filename.csv
cut
コマンドを使用してカンマ区切りの CSV ファイルから特定の列を抽出します。-d ‘,’オプションは、フィールドの区切り文字をカンマに設定し、-f 2オプションは、2番目の列を抽出することを指定します。
ファイル内のテキストを置換する
sed 's/foo/bar/g' filename
sed
コマンドを使用してファイル内のテキストを置換します。s/foo/bar/g
は、foo
をbar
に置換するという意味です。g
フラグは、行内のすべての一致箇所を置換するために使用されます。
ファイルの行数をカウントして結果を別のファイルに保存する
wc -l < filename | tee line_count.txt
まずwc -l
コマンドを使用してファイルの行数をカウントします。その結果をtee
コマンドを使って標準出力とline_count.txt
というファイルに同時に書き込みます。teeコマンドは、データを標準出力とファイルの両方に出力するために使用されます。
ファイルの内容を逆順に表示して特定の行を抽出する
tac filename | sed -n '5,10p'
tac
コマンドを使用してファイルの内容を逆順に表示し、sed
コマンドを使用して5行から10行までを抽出します。-n
オプションは、sed
コマンドがデフォルトで行を出力しないようにするために使用されます。5,10p
は、5行から10行までの行を出力するという意味です。
ファイル内のテキストを置換して結果を別のファイルに保存する
sed 's/foo/bar/g' filename | tee replaced.txt
sed
コマンドを使用してファイル内のテキストを置換し、その結果をtee
コマンドを使って標準出力とreplaced.txt
というファイルに同時に書き込みます。s/foo/bar/gは、fooをbarに置換するという意味です。
ディレクトリ内のファイル数をカウントする
ls -l | grep "^-" | wc -l
ls -l
コマンドを使用してディレクトリ内のファイル一覧を表示し、g
repコマンドを使ってファイルの行をフィルタリングします。^は行の先頭を表し、-は通常のファイルを示します。そして、wc -l
コマンドを使用して行数をカウントします。
ログファイルから特定のキーワードを検索する
grep "keyword" logfile | tail -n 10
grep
コマンドを使用してログファイルから特定のキーワードを検索し、tail -n 10
コマンドを使って最後の10行のみを表示します。tail -n 10は、ファイルの末尾から10行を表示するという意味です。
ログファイルから特定のキーワードを検索し該当行の数をカウント
grep -c "error" $(find /var/log -name "*.log")
findコマンドを使用して/var/logディレクトリ以下のすべての.logファイルを検索し、grepコマンドを使用して各ファイルで”error”キーワードを検索します。結果はカウントされ、最終的な合計数が表示されます。
ファイルの内容を並び替えて重複を削除する
sort filename | uniq
sort
コマンドを使用してファイルの内容を並び替え、uniq
コマンドを使って重複した行を削除します。sort
コマンドによってデータが昇順にソートされ、uniq
コマンドによって連続する重複行が削除されます。
ファイルをバックアップ
cp -p file.txt{,.bak}
ブレース展開を使用してfile.txtとfile.txt.bakという2つのファイル名を展開し、cpコマンドを使用してファイルをバックアップします。
条件に基づいて値を出力する
age=25
echo "You are $(($age >= 18 ? "an adult" : "a minor"))"
変数ageの値に基づいて、三項演算子を使用して条件に応じたメッセージを出力します。
複数のファイルを削除
{ rm file1.txt, file2.txt, file3.txt; } 2>/dev/null
グルーピングとGlob展開を使用して、一度に複数のファイルを削除します。rmコマンドによる削除時のエラーメッセージは2>/dev/nullで無視されます。
カラフルな出力を行う
echo -e "\e[31mThis is red text\e[0m"
エスケープシーケンスを使用して、ターミナルでカラフルな出力を行います。\e[31mは赤色のテキストを指定し、\e[0mはリセットしてデフォルトのテキスト色に戻します。
変数を展開し計算
num1=10
num2=5
result=$((num1 + num2))
echo "The result is: $result"
変数num1とnum2の値を使用して算術式展開を行い、足し算の結果を変数resultに代入し、最後にその値を表示します。
ファイル内の数値の合計を計算する
awk '{sum += $1} END {print sum}' filename
awk
コマンドを使用してファイル内の数値の合計を計算します。各行の数値は$1
で参照され、sum
という変数に加算されます。最後にEND
パターンが指定され、print sum
によって合計値が出力されます。
ファイル内の数値の平均を計算する
awk '{sum += $1} END {print sum/NR}' filename
前の例と同様にawk
コマンドを使用してファイル内の数値の合計を計算しますが、最後にNR
を使用して行数を取得し、合計値を行数で割って平均値を計算しています。
ファイル内の数値の最大値を見つける
awk 'NR == 1 {max = $1} $1 > max {max = $1} END {print max}' filename
awk
コマンドを使用してファイル内の数値の最大値を見つけます。最初の行(NR == 1
)の値をmax
として初期化し、それ以降の各行で現在の最大値と比較し、より大きい場合に更新します。最後に最大値が出力されます。
ファイル内の数値の最小値を見つける
awk 'NR == 1 {min = $1} $1 < min {min = $1} END {print min}' filename
前の例と同様にawk
コマンドを使用してファイル内の数値の最小値を見つけます。最初の行(NR == 1
)の値をmin
として初期化し、それ以降の各行で現在の最小値と比較し、より小さい場合に更新します。最後に最小値が出力されます。
awkコマンドは、下記の記事で詳しく解説しています。併せてご覧になることで、知識が深まると思いますので、是非ご覧ください。
![速習Linux – 文字列の置換・抽出・検索と正規表現【sed・awk・grep】](https://i0.wp.com/www.yuus-program.com/storage/2023/05/welcome-little-one-13.png?resize=320%2C180&ssl=1)
現在の日付を取得
date +%Y-%m-%d
date
コマンドを使用して現在の日付を表示します。+%Y-%m-%d
は、年-月-日の形式で日付を表示するという意味です。
日付を取得(コマンド置換使用)
current_date=$(date +%Y-%m-%d)
echo "Today's date is ${current_date}"
コマンド置換を使用してdateコマンドの出力(現在の日付)を変数current_dateに代入し、その後、変数展開を使用して日付を表示します。
日付のフォーマットを変換
date -d "2023-06-01" +%Y%m%d
date
コマンドを使用して指定した日付のフォーマットを変換します。-d
オプションを使用して日付を指定し、+%Y%m%d
を使用して年月日の形式に変換しています。例では、”2023-06-01″が”20230601″に変換されます。
特定の日数を加算または減算
date -d "2023-06-01 +5 days" +%Y-%m-%d
date
コマンドを使用して指定した日付に特定の日数を加算します。-d
オプションを使用して日付を指定し、+5 days
を使用して5日後の日付を計算します。結果は”2023-06-06″と表示されます。
2つの日付の間の日数を計算
startdate="2023-06-01"
enddate="2023-06-10"
echo $(( ($(date -d $enddate +%s) - $(date -d $startdate +%s)) / 86400 ))
2つの日付の間の日数を計算します。startdate
とenddate
にそれぞれ日付を設定し、date
コマンドを使用してそれぞれの日付をUNIX時間に変換します。そして、日数を計算するために2つのUNIX時間の差を取り、86400(1日の秒数)で割ります。
バイナリファイルのヘックスダンプを表示
xxd filename
xxd
コマンドを使用してバイナリファイルの内容をヘックスダンプ形式で表示します。各バイトの16進数値とそれに対応するASCII文字が表示されます。
バイナリファイルのサイズを表示
stat -c "%s" filename
stat
コマンドを使用してバイナリファイルのサイズを表示します。-c "%s"
オプションは、ファイルサイズのみを出力するように指定しています。
バイナリファイル内で特定の文字列を検索
strings filename | grep "search_string"
stringsコマンドを使用してバイナリファイル内のテキスト文字列を抽出し、grepコマンドを使用して特定の文字列を検索します。search_stringは検索したい文字列に置き換えてください。
バイナリファイルのエントリポイントを表示
readelf -h filename | grep "Entry point"
readelfコマンドを使用してバイナリファイルのヘッダ情報を表示し、grepコマンドを使用してエントリポイントの行を抽出します。バイナリファイルの実行が開始されるアドレスがエントリポイントとして表示されます。
ディレクトリごとのディスク使用量を表示
du -h <directory_path>
du
コマンドを使用して指定したディレクトリのディスク使用量を表示します。-h
オプションは、人間が読みやすい形式でサイズを表示するためのものです。
ディレクトリごとのディスク使用量を合計し、降順で表示
du -h <directory_path> | sort -hr
前の例と同様にdu
コマンドを使用してディレクトリごとのディスク使用量を表示しますが、sort
コマンドを使って結果を降順にソートします。-r
オプションは逆順ソートを行うためのものです。
ディレクトリ内のファイル数とディレクトリ数をカウント
echo "Files: $(find . -type f | wc -l), Directories: $(find . -type d | wc -l)"
findコマンドを使用してカレントディレクトリ以下のファイルとディレクトリの数をそれぞれカウントします。最終的な結果はメッセージとして表示されます。
ディスク使用量の合計を計算
du -hs <directory_path> | awk '{print $1}'
du
コマンドを使用して指定したディレクトリのディスク使用量を表示しますが、-s
オプションを使って合計のみを表示します。awk
コマンドを使用して出力の最初の列(ディスク使用量)のみを抽出します。
ファイルのサイズを合計
find <directory_path> -type f -exec du -ch {} + | grep total$ | awk '{print $1}'
find
コマンドを使用して指定したディレクトリ内のすべてのファイルを見つけ、du
コマンドを使って各ファイルのサイズを計算します。その後、grep
コマンドでtotal
というキーワードを持つ行(合計行)を抽出し、awk
コマンドで出力の最初の列(合計サイズ)のみを表示します。
システム上で最もディスク使用量の大きいディレクトリを表示
du -hs /* | sort -hr | head -n 1
duコマンドを使用してルートディレクトリ以下のディレクトリのディスク使用量を計算し、sortコマンドとheadコマンドを組み合わせて結果をソートし、最もディスク使用量の大きいディレクトリを表示します。
エクセルファイルからデータを集計
厳密には、ワンライナーではありませんが需要がありそうなので、書いておきます。
エクセルファイルからデータを集計するためには、ワンライナーだけではなく、適切なツールを使用する必要があります。以下に、エクセルファイルからデータを集計するための一般的な手順は下記の様に行います。
エクセルファイルをCSV形式に変換
エクセルファイルをCSV形式に変換することで、テキストベースのデータとして扱えるようになります。Bashでは、xlsx2csv
ツールを使用することができます。
xlsx2csv input.xlsx > output.csv
CSVファイルをBashワンライナーで処理
変換されたCSVファイルをコマンドで処理し、必要な集計を行います。Bashの組み込みコマンドや他のコマンドラインツールを使用してデータを解析・集計します。
# 例: 列ごとの合計を計算する
awk -F',' '{ sum += $1 } END { print sum }' input.csv
集計結果を表示または出力
得られた集計結果を表示したり、別のファイルに出力することができます。
# 集計結果を表示する
awk -F',' '{ sum += $1 } END { print sum }' input.csv
# 集計結果をファイルに出力する
awk -F',' '{ sum += $1 } END { print sum }' input.csv > result.txt
以上の手順を使用して、エクセルファイルからデータを集計することができます。エクセルファイルをCSV形式に変換し、それをシェルで処理することで、必要な統計や集計を行うことができます。
まとめ
如何でしたか?基本の説明をしっかりと行なっておきたかったので、解説が長くなり過ぎてしまいました。Linuxのシェルの基本を理解しておくと、更に高度な使い方や、シェルスクリプトをファイルに作り、CRONなどでバッチ処理をさせる際にも必ず役に立ちます。
今後は、シェルスクリプトの入門ガイドなども併せて記事にしていきたいと思います。他にもこんな事を知りたいなどのご要望がありましたら、詳しく解説しますので、お気軽にコメントをください。