シェルワンライナー #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を含む行で終わる範囲の行が出力されます。範囲外の行は出力されません。
思ったこと
シェルワンライナー #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 をつけてビルドエラーが起きた
状況
- --coverageをコンパイルオプションに追加するビルドに失敗した。
- 一方で、コンパイルオプションに-fprofile-arcs, -ftest-coverage、リンカオプションに-lgcov だとビルドが成功した
- 公式ページには --coverageをコンパイルオプションに指定すると「コンパイルオプションに-fprofile-arcs, -ftest-coverage、リンカオプションに-lgcov」を指定した場合と同じ動作になる。と書いてある
ChatGPTさんに質問した結果
-fprofile-arcs
および -ftest-coverage
オプションを使用することで、--coverage
オプションと同様の結果が得られます。--coverage
オプションは、実際にはこれらのオプションの組み合わせを指定しているだけです。ただし、いくつかのプロジェクトや環境では、これらのオプションを個別に指定する方が正しく動作することがあります。
以下のような理由で、個別にオプションを指定した場合にビルドが成功する場合があります。
環境やツールチェーンの違い: ある環境やツールチェーンでは、
--coverage
オプションが正しく動作しないことがあります。この場合、個別にオプションを指定することで問題を回避できることがあります。コンパイラのバージョン: コンパイラのバージョンによっては、
--coverage
オプションの動作が異なることがあります。古いコンパイラでは、--coverage
オプションが完全にサポートされていないかもしれません。そのため、個別のオプションを指定することで、問題が解決されることがあります。リンク順序の問題:
--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を除く)と相対パスが取得され、カレントディレクトリにそれぞれのディレクトリが作成されます。
grep -o '[^/]*\.dir'
sed 's/\.dir$//'
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
のディレクトリ名に置き換えられ、相対パスが抽出されます。
- このコマンドは、入力文字列(ここではディレクトリパス)から
これらの grep
と sed
コマンドを組み合わせることで、ディレクトリパスから末尾が .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
- でまずは出力する
- その後、なんか変数設けて足し算する
- 2列目の先頭に *がついている
だめな回答
- あれ?答えが違うじゃん?と思ったら、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
- 思ったこと
補足
回答のところに書いたので特になし。