ファイルの先頭から行を抽出する

ファイルの先頭から行を抽出する

私は任務があります:

現在のディレクトリにあるすべての ".c"ファイルの最初の3行コードを場所引数として指定された一時ファイルにコピーするシェルスクリプトを作成します。一時ファイルの内容を表示します。

最初に「.c」で終わるファイルを作成し、その拡張子をhead -3 *.c> touch $1持つすべてのファイルの最初の3行をコピーできましたが、正しく実行しているかどうか、または問題を解決できるかどうかを知りたいです。別の方法では。

答え1

head一見すると、方法は良く見えますが、head同時に複数のファイルを実行するときにユーティリティがファイル名を含む出力にヘッダーを入れる方法がわかります。

おそらくあなたは可能このタイトルを受け取りたくない場合は、課題のテキストに従ってください。

touchまったく使用する必要はありません。私は他の宿題を解決しようとしたときに人々が時々「ファイルを作成」​​し、それを実行するために使用する適切なファイルにデータをリダイレクトする必要があると思うことを発見しましたtouch

リダイレクトを使用すると、>filenameそのファイルがまだ存在しない場合は、そのファイルが自動的に作成されます(現在のディレクトリの権限が許可している場合)。ファイルが存在する場合は切り捨てられます(空になります)。

headいいですね。出力でファイル名を含むヘッダーの生成をどのように停止しますか?さて、Linuxシステムを使っているなら、おそらくGNUがあるでしょうhead。この実装headでは非標準 -qオプションタイトルを抑制します。

したがって、スクリプトは次のように書くことができます。

#!/bin/sh

head -q -n 3 -- *.c >"$1"

...ユーザーがGNUを持っているとしますhead。ファイル名ワイルドカードパターンと一致するファイル名がダッシュで始まる--場合は、「コマンドラインオプションの終わり」シグナルをエクスポートする必要があります。ダッシュはオプション文字列の先頭と見なすことができます。head*.c

別の方法は、現在のディレクトリでファイルを明示的に参照するhead -q -n 3 ./*.c場所を使用することです。./*.cすべてのファイル名はダッシュで始まるので、引数が./ダッシュで始まる可能性がないため、もはや--必要ありません。これを行う方法はあなた次第ですが、--help.c現在のディレクトリ()touch -- --help.cにあるファイルを使用してスクリプトをテストしてください。

/bin/sh代わりに、スクリプトのインタプリタを使用することにしました/bin/bash。これは、スクリプトが配列、プロセス置換、中括弧拡張、正規表現マッチングなどのbash必要なものを使用していないためです。bash

Linuxシステムを使用しない場合、またはPOSIX標準に準拠して作成する場合持ち運べる-qスクリプトと一緒に使用しないでくださいhead

代わりに、ファイルを繰り返してhead個々のファイルに使用できます。

#!/bin/sh

for name in *.c; do
    head -n 3 -- "$name"
done >"$1"

出力をリダイレクトする方法を確認してください。回報ファイルとして。

また、ループを使用してこの問題を解決すると、次のような場合でもスクリプトが正しく機能することがわかります。数千ファイル数.c。ループがないと、headシェルが何千ものファイル名拡張子をすべて使用して実行しようとすると、「引数リストが多すぎます」というエラーが発生する可能性があります。これ欠点head1つの問題は、特に何千ものファイルがある場合、各ファイルを個別にターゲットにするのが非常に遅いことです。

次の問題は、スクリプトユーザーが正しいパラメータを提供しない場合に何が起こるのかを判断することです。ユーザーが既に存在するファイル名を使用するか、ファイル名がまったく存在しない状態でこのスクリプトを実行するとします。何もせずにこれをつかんで不平衡にしましょう:

#!/bin/sh

if [ "$#" -ne 1 ]; then
    printf 'expecting 1 argument, got %d\n' "$#" >&2
    exit 1
elif [ -e "$1" ]; then
    printf 'the name "%s" already exist, refusing to over-write\n' "$1" >&2
    exit 1
fi

for name in *.c; do
    head -n 3 -- "$name"
done >"$1"

これはif、スクリプトに提供されたコマンドライン引数の数を最初にテストするステートメントを紹介します。まったく一つでなければ、文句を言ってやめてください。一つですが、すでに存在する名前を指している場合は、文句を言って終了します。

診断メッセージ(エラーなど)は標準エラーストリームに書き込む必要があります。ここでは、リダイレクトされた出力を使用してこれを行います>&2。また、続行できないことが明らかな場合は、ゼロ以外の終了状態でスクリプトを終了します。これにより、スクリプトが正常に実行されたかどうかをテストできます。

if ./your-script.sh hello world; then
    echo ok
else
    echo something went wrong
fi

残りの問題は、次の状況を処理することです。いいえ .c現在のディレクトリのファイルです。これが発生すると、スクリプトがどのように奇妙なエラーを生成するのかがわかります。

head: *.c: No such file or directory

のようなパターンが*.c何も一致しない場合は、拡張されていないままです。ループに小さなテストを追加することでこの問題を解決できます。

for name in *.c; do
    [ ! -e "$name" ] && continue
    head -n 3 -- "$name"
done >"$1"

これは、「ファイルが$name存在しない場合、このループの繰り返しをスキップします」を意味します。

bashこれをスクリプトとして作成すると、元のループを維持し、nullglobループの前にシェルオプションを設定して、shopt -s nullglobシェルが一致しないパターンを拡張されていないままにするのではなく削除することができます。

スクリプトを実行すると、.c現在のディレクトリにファイルが存在しない場合、エラーは生成されません。ただし、これが発生すると、出力ファイルは空になります。これが望ましくない場合は、*.cループを開始して出力ファイルにリダイレクトする前に、実際に一致するものがあるかどうかをテストできます。

outfile=$1

set -- *.c

if [ -e "$1" ]; then
   for name do
       head -n 3 -- "$name"
   done >"$outfile"
fi

ここでは、位置パラメータ(スクリプトに指定された引数)をすべてのファイルの名前にオーバーライドするので、まず出力ファイルの名前を別の変数に保存します.c(これはを通じて行われますset)。

最初の位置引数が既存のファイル名の場合、ループが実行され、出力ファイルを作成/切り捨てることができます。

ループに繰り返す項目がない場合(最後のコード部分に示すように)、デフォルトでは位置引数のリストが繰り返されます。私たちは、で始まる名前でこのリストを設定しました.c


他のバリエーションはheadまったく使用せず、または他のコマンドなどの同等のコマンドを使用することsed 3qですawk '1; NR == 3 { exit }'。ただし、このユーティリティを使用すると、やりたいことheadについて明確なアイデアを得ることができます。

関連情報