シェルワンライナー #9

問題

apacheのログを決まった期間抜き出す問題

回答

 cat log_range.log | sed -n '/24\/Dec\/2016 21:..:../,/25\/Dec\/2016 03:..:../p'

sed -n '/hoge/,/piyo/p' について

sed コマンドは、Unixオペレーティングシステムで使われるストリームエディタで、テキストファイルの編集や処理を行うのに役立ちます。sed は、テキストファイルを1行ずつ読み込んで、指定されたパターンやコマンドに従って処理し、結果を標準出力に出力します。

この場合の sed -n '/2/,/4/p' コマンドは、以下のように動作します。

  • -n オプションは、sed に対して、自動的な出力を抑制するよう指示します。これにより、明示的に p コマンド(後述)で指定された行だけが出力されるようになります。
  • '/2/,/4/' は、範囲を指定しています。この範囲は、/2/ で始まり、/4/ で終わります。ここで、/2/ は「2 を含む行」にマッチし、/4/ は「4 を含む行」にマッチします。したがって、この範囲は、2 を含む行から始まり、4 を含む行で終わるすべての行になります。
  • p コマンドは、現在の行を印刷(出力)するよう指示します。この場合、範囲内の行が対象となります。

このコマンドが適用されると、テキストファイルの中で2を含む行から始まり、4を含む行で終わる範囲の行が出力されます。範囲外の行は出力されません。

思ったこと

  • しらない方法だったので、さっさと答え見て正解。
  • sedは置換するだけだと思ってたら、正規表現で範囲指定して抽出みたいなことができるんだなぁ

シェルワンライナー #8

問題

183.YY.129.XX - - [07/Nov/2017:22:37:38 +0900]
...

こういう入力で午前と午後の行を数える

回答

cat access.log | awk '{split($4,a,":"); print a[2]}' | awk '{if($1>=0 && $1<12) am++; else pm++; } END{print "AM:" am ", PM:" pm}'

出力結果

AM:2, PM:3

  • $4をsplitで”:”で区切って配列に入れる。
  • 時間の要素のみ出力
  • 0-11だったら AM, 12-23だったらPMでカウント
  • AM: x 行、PM: y みたいな感じで出力

補足

  • テキストに似た答えはなかった。
  • テキスト答えは
    • awk -F: でコロンを空白に置き換える
    • $(NF-2) で列数 - 2 即ち 後ろから3つ目で時間部分を抽出し出力
    • それをawkのフィルタで午前、午後の条件、アクションを条件に応じて「午前」or「午後」と出力
    • その出力結果を sort
    • その出力結果を uniq -c で集計して出力

エクセルでマクロに割り当てるショートカットのおすすめ

なやんだのでchatgptにきいた。 良くない答えが帰ってきた。

結論

挙げられた候補のうちエクセルマクロは下記パターン以外は使えません。 - Ctrl + Shift + (任意のアルファベット)

ChatGPT回答

一般的に、他のショートカットキーと被らないようにするためには、以下のようなキーの割り当てがおすすめです。

  • Ctrl + Alt + (任意のアルファベット)
  • Ctrl + Shift + (任意のアルファベット)
  • Alt + Shift + (任意のアルファベット)
  • Ctrl + Alt + Shift + (任意のアルファベット)

これらのキーは、Excelの標準的なショートカットキーとは異なるため、他のショートカットキーとの衝突が少ない傾向があります。

ただし、キーボードの種類や国によっては、これらのキーの組み合わせが使用できない場合があります。そのため、実際に使用可能なキーの範囲で割り当てるようにしてください。

また、よく使用するマクロに割り当てることをおすすめします。そのためには、Excelを使っているときに何度も使用する操作を自動化するマクロを作成すると良いでしょう。

--coverage をつけてビルドエラーが起きた

状況

ChatGPTさんに質問した結果

-fprofile-arcs および -ftest-coverage オプションを使用することで、--coverage オプションと同様の結果が得られます。--coverage オプションは、実際にはこれらのオプションの組み合わせを指定しているだけです。ただし、いくつかのプロジェクトや環境では、これらのオプションを個別に指定する方が正しく動作することがあります。

以下のような理由で、個別にオプションを指定した場合にビルドが成功する場合があります。

  1. 環境やツールチェーンの違い: ある環境やツールチェーンでは、--coverage オプションが正しく動作しないことがあります。この場合、個別にオプションを指定することで問題を回避できることがあります。

  2. コンパイラのバージョン: コンパイラのバージョンによっては、--coverage オプションの動作が異なることがあります。古いコンパイラでは、--coverage オプションが完全にサポートされていないかもしれません。そのため、個別のオプションを指定することで、問題が解決されることがあります。

  3. リンク順序の問題: --coverage オプションを使用すると、コンパイラが追加するライブラリのリンク順序が変更されることがあります。これにより、リンクエラーが発生することがあります。個別のオプションを指定することで、リンク順序が適切になり、エラーが解決されることがあります。

最終的に、コンパイルオプションに -fprofile-arcs-ftest-coverage を、リンクオプションに -lgcov を追加することでビルドが成功した場合、これらのオプションを使用してコードカバレッジ情報を収集できます。問題が解決されたのであれば、この設定をそのまま使用して問題ありません。

思ったこと

  • 3つめの理由が怪しいかな。
    • リンカが通らないと怒られていたので。
  • 先輩にコメントもらうまで、その対応方法を試さなかったので、次回からは同様な事象(まとめてやってくれるオプションの挙動がおかしい)が起こったときは、個別にオプション指定してみるようなアクションを取れるようにしたい。

shell script 特定ディレクトリをスキップ

# スキップしたいディレクトリ名をスペース区切りで指定
skip_dirs="hoge piyo"

# 現在のディレクトリ以下で、名前が '*.dir' にマッチするディレクトリを検索し、それぞれに対してループを実行
for path in $(find . -name '*.dir' -type d); do
    # path のベース名(ディレクトリ名)を取得
    dir_name=$(basename "$path")

    # スキップ対象のディレクトリかどうかをチェック
    case " $skip_dirs " in
        *" $dir_name "*) continue ;;
    esac

    # coverage_report 以下に dir_name と同じ名前のディレクトリを作成
    cov_dir="coverage_report/$dir_name"

    # カバレッジレポート用のファイル名を指定
    cov_file="$cov_dir/unit_test.info"

    # cov_dir が存在しない場合は作成(エラーメッセージは無視)
    mkdir -p "./$cov_dir" > /dev/null 2>&1

    # lcov コマンドを使ってカバレッジレポートを生成し、cov_file に保存
    # lcov_branch_coverage=1 を指定して、ブランチカバレッジも含める
    lcov -d "$path" -c --rc lcov_branch_coverage=1 -o "./$cov_file"
done

このスクリプトでは、skip_dirs変数にスキップしたいディレクトリ名をスペース区切りで追加することで、後から簡単にスキップ対象のディレクトリを追加できます。スキップ対象のディレクトリが増えた場合は、skip_dirs変数にディレクトリ名を追加してください。


追加要件に基づいて、スクリプトを変更しました。$dir_nameの1つ上のディレクトリ名がtoolの場合、処理をスキップします。

# 現在のディレクトリ以下で、名前が '*.dir' にマッチするディレクトリを検索し、それぞれに対してループを実行
for path in $(find . -name '*.dir' -type d); do
    # path のベース名(ディレクトリ名)を取得
    dir_name=$(basename "$path")

    # 1つ上のディレクトリのパスを取得
    parent_path=$(dirname "$path")

    # 1つ上のディレクトリの名前を取得
    parent_name=$(basename "$parent_path")

    # 1つ上のディレクトリ名が "tool" の場合、処理をスキップ
    if [ "$parent_name" = "tool" ]; then
        continue
    fi

    # coverage_report 以下に dir_name と同じ名前のディレクトリを作成
    cov_dir="coverage_report/$dir_name"

    # カバレッジレポート用のファイル名を指定
    cov_file="$cov_dir/unit_test.info"

    # cov_dir が存在しない場合は作成(エラーメッセージは無視)
    mkdir -p "./$cov_dir" > /dev/null 2>&1

    # lcov コマンドを使ってカバレッジレポートを生成し、cov_file に保存
    # lcov_branch_coverage=1 を指定して、ブランチカバレッジも含める
    lcov -d "$path" -c --rc lcov_branch_coverage=1 -o "./$cov_file"
done

はい、dirnameコマンドは、パスがディレクトリであってもファイルであっても使用できます。このコマンドは、指定されたパスから1つ上のディレクトリのパスを返します。dirnameコマンドはファイルやディレクトリを削除するわけではなく、指定されたパスの親ディレクトリのパスを出力するだけです。

例えば、以下のようにdirnameコマンドを使用できます。

$ dirname /home/user/documents/work/file.txt
/home/user/documents/work

$ dirname /home/user/documents/work/dir
/home/user/documents/work

このスクリプトでは、$pathはディレクトリまでのパスが含まれています。dirnameコマンドを使って、そのディレクトリの1つ上のディレクトリのパスを取得しています。そして、その1つ上のディレクトリの名前をbasenameコマンドで取得しています。


# 現在のディレクトリ以下で、名前が '*.dir' にマッチするディレクトリを検索し、それぞれに対してループを実行
for path in $(find . -name '*.dir' -type d); do
    # path のベース名(ディレクトリ名)を取得
    dir_name=$(basename "$path")

    # ディレクトリ階層に "tool" が含まれるかチェック
    if echo "$path" | grep -q "/tool/"; then
        # "tool" が含まれる場合、処理をスキップ
        continue
    fi

    # coverage_report 以下に dir_name と同じ名前のディレクトリを作成
    cov_dir="coverage_report/$dir_name"

    # カバレッジレポート用のファイル名を指定
    cov_file="$cov_dir/unit_test.info"

    # cov_dir が存在しない場合は作成(エラーメッセージは無視)
    mkdir -p "./$cov_dir" > /dev/null 2>&1

    # lcov コマンドを使ってカバレッジレポートを生成し、cov_file に保存
    # lcov_branch_coverage=1 を指定して、ブランチカバレッジも含める
    lcov -d "$path" -c --rc lcov_branch_coverage=1 -o "./$cov_file"
done

このスクリプトでは、echo "$path" | grep -q "/tool/"を使って、ディレクトリ階層にtoolが含まれているかどうかをチェックしています。grep -qは、マッチする行がある場合には何も出力せず、終了ステータスでマッチの有無を返します。toolが含まれている場合、continueコマンドによって処理をスキップします。

メモ

やりたいことをchatgptにきいた。 後で記事にするためまとめ直す。

以下のスクリプトでは、find コマンドを使って、*.gcno ファイルを検索し、結果を配列 find_output に格納しています。

#!/bin/bash

# find コマンドで *.gcno ファイルを検索
find_output=($(find . -name '*.gcno'))

# 各ディレクトリパスを処理
# for path in "${find_output[@]}"; do # コマンド直接書いちゃうと下記。

  # ディレクトリパスから末尾が '.dir' のディレクトリ名を抽出
  dir_name_with_ext=$(echo "${path}" | grep -o '[^/]*\.dir')

  # '.dir' を除いたディレクトリ名を取得
  dir_name=$(echo "${dir_name_with_ext}" | sed 's/\.dir$//')

  # .dirのディレクトリ名までの相対パスを別の変数に格納
  relative_path=$(echo "${path}" | sed "s/\(.*\)${dir_name_with_ext}.*/\1${dir_name_with_ext}/")

  # .dir を除いたディレクトリ名でカレントディレクトリにディレクトリを作成
  mkdir -p "./${dir_name}"

  echo "ディレクトリ名(.dirを除く): ${dir_name}"
  echo "相対パス: ${relative_path}"
  echo "-----"
done

このスクリプトを実行すると、find コマンドで検索された各ディレクトリパスに対して、ディレクトリ名(.dirを除く)と相対パスが取得され、カレントディレクトリにそれぞれのディレクトリが作成されます。

  1. grep -o '[^/]*\.dir'
    • このコマンドは、入力文字列(ここではディレクトリパス)から末尾が .dir の部分文字列を抽出します。
    • o オプションは、一致した部分文字列のみを出力します。
    • 正規表現 [^/]*\.dir は、以下を意味します。
      • [^/]:スラッシュ / 以外の任意の文字
      • ``:直前の文字が0回以上繰り返される
      • \.dirリテラル.dir 文字列
    • この正規表現は、スラッシュ以外の文字が0回以上繰り返され、最後に .dir が続く部分文字列に一致します。
  2. sed 's/\.dir$//'
    • このコマンドは、入力文字列(ここでは末尾が .dirディレクトリ名)から .dir を取り除きます。
    • s/パターン/置換/パターン に一致する部分を 置換 で置き換える
    • 正規表現 \.dir$ は、以下を意味します。
      • \.dirリテラル.dir 文字列
      • $:文字列の末尾
    • この正規表現は、文字列の末尾に .dir がある部分に一致します。
    • 置換部分が空文字列なので、一致した部分(.dir)が削除されます。
  3. sed "s/\(.*\)${dir_name_with_ext}.*/\1${dir_name_with_ext}/"
    • このコマンドは、入力文字列(ここではディレクトリパス)から .dirディレクトリ名までの相対パスを抽出します。
    • s/パターン/置換/パターン に一致する部分を 置換 で置き換える
    • 正規表現 \(...\):カッコ内の部分に一致する部分をキャプチャ(後で参照できる)
    • .*:任意の文字が0回以上繰り返される
    • この正規表現は、任意の文字列(キャプチャされる)と、末尾が .dirディレクトリ名に一致します。
    • 置換部分は \1${dir_name_with_ext} で、\1 は最初のキャプチャグループ(任意の文字列)を参照し、${dir_name_with_ext} は末尾が .dirディレクトリ名を参照します。
    • 結果として、一致した部分(ディレクトリパス全体)が、キャプチャされた任意の文字列と末尾が .dirディレクトリ名に置き換えられ、相対パスが抽出されます。

これらの grepsed コマンドを組み合わせることで、ディレクトリパスから末尾が .dirディレクトリ名を抽出し、.dir を取り除いたディレクトリ名を取得し、.dirのディレクトリ名までの相対パスを別の変数に格納しています。


指定された find コマンドを使用して、*.dir のディレクトリを検索し、相対パスをそのまま利用するように修正しました。以下が修正されたシェルスクリプトです。

#!/bin/bash

# find コマンドで *.dir ディレクトリを検索
find_output=($(find . -name '*.dir' -type d))

# 各ディレクトリパスを処理
for path in "${find_output[@]}"; do
  # ディレクトリパスから末尾が '.dir' のディレクトリ名を抽出
  dir_name_with_ext=$(basename "${path}")

  # '.dir' を除いたディレクトリ名を取得
  dir_name=$(echo "${dir_name_with_ext}" | sed 's/\.dir$//')

  # .dirのディレクトリ名までの相対パスをそのまま利用
  relative_path="${path}"

  # .dir を除いたディレクトリ名でカレントディレクトリにディレクトリを作成
  mkdir -p "./${dir_name}"

  echo "ディレクトリ名(.dirを除く): ${dir_name}"
  echo "相対パス: ${relative_path}"
  echo "-----"
done

#!/bin/bash

# find コマンドで *.dir ディレクトリを検索
find_output=($(find . -name '*.dir' -type d))

# 各ディレクトリパスを処理
for path in "${find_output[@]}"; do
  # ディレクトリパスから末尾が '.dir' のディレクトリ名を抽出
  dir_name_with_ext=$(basename "${path}")

  # '.dir' を除いたディレクトリ名を取得
  dir_name=$(echo "${dir_name_with_ext}" | sed 's/\.dir$//')

  # .dirのディレクトリ名までの相対パスをそのまま利用
  relative_path="${path}"

  # .dir を除いたディレクトリ名でカレントディレクトリにディレクトリを作成
  mkdir -p "./${dir_name}"

  # lcov コマンドで .info ファイルを作成
  lcov --capture --directory "${path}" --output-file "./${dir_name}/coverage.info"

  echo "ディレクトリ名(.dirを除く): ${dir_name}"
  echo "相対パス: ${relative_path}"
  echo "-----"
done

シェルワンライナー #7

問題

与えられたテキストに対する税率の計算。
2019/10以前なら8%。それ以降ならば、8%と10%。商品名に*がついているのは8%。

1 20190901 ゼロカップ大関 10000 
2 20190902 *キャベツ二郎 130
3 20191105 外食 13000
4 20191106 ストロングワン 13000
5 20191106 *ねるねるねるねる 30
6 20190912 外食 13000

回答

自分の回答

  • 考えた段取り

    • 2列目の先頭に *がついている 3列目 * 1.08
    • 2列目の先頭に *がついている 3列目 * 1.10
    • でまずは出力する
    • その後、なんか変数設けて足し算する
  • だめな回答

    • あれ?答えが違うじゃん?と思ったら、20191001より前ならば税率を8%としないといけないっぽい。
cat kakeibo.txt | awk 'BEGIN{sum=0}{if($2 ~ /^\*/){sum += int($3*1.08)} else {sum+=int($3*1.10)}} END{print sum}'
  • OKな回答
    • もう一個日付で切り替える条件のif文をあたまにつけた。
cat kakeibo.txt | awk 'BEGIN{sum=0} {if($1>20191001){if($2 ~ /^\*/){sum += int($3*1.08)} else {sum+=int($3*1.10)}} else{sum += int($3*1.08)}} END{print sum}'

({}がどこに対応するかかなり分かりづらい…)

テキストの回答

#わかりやすさのために、パイプ毎に改行してます
cat kakeibo.txt | 
awk '{if($1<20191001||$2 ~"^*"){print $0,1.08} else{print $0,1.1}}' | 
awk '{print int($3*$4)}' | 
numsum
  • 思ったこと
    • 日付の条件のところはORでつなげば良いじゃん。なるほど
    • 変数使って一回で計算せずとも、パイプで出力をつなぐ形にすれば変数すらいらない。なるほど
    • numsumコマンドっていうのがあるのか。なるほど
      • でもデフォルトでは入ってなかったので、 sudo apt install num-utils でインストールした
    • awkコマンドのフィルタで正規表現するときについて / / で囲う場合と ” ” で囲う場合がある
      • “ ” で囲う時は中が文字列リテラルとして認識されるので、 * をフィルタしたいときにエスケープが不要らしい。
    • ほんとの答えはif-elseじゃなくて三項演算子使ってた

補足

回答のところに書いたので特になし。

感想

  • 自分で一旦考えながらやると、答えをみたときに学びが深い。
    • if文とか一行で書くと、インデントつけれなくてかなり辛い。。。
    • テキストは三項演算子使ってたけど、if文だと括弧の対応が分かりづらいというのもあるからなのかな?
  • awkの構文なかなか、記憶できない。。。