シェルワンライナー: 2.1.b. 変数を使った計算

1. 2.1.d. 変数を使った計算 

  • Bashの変数は単なる文字列だが、一時的に文字列を数字として扱える。
  • Bashでは$(())の中に計算式を書くと計算ができる。
    • この記号は 算術式展開と呼ばれる
  • 算術式展開で使える演算子man bash「算術式評価」の項目にある。
    • 括弧の中の変数に$をつける必要はない

1.1. 練習問題

1.1.1. 問題と答え

----準備----
$ a=6
$ b=2
----ここから答え----
echo $((a+b)) $((b-a)) $((a*b)) $((a/b)) $((b<<a))

1.1.2. 補足

  • << は左シフト
  • 他にもANDとかORとかもいける

シェルワンライナー #2.1.c: 文字列の連結と置換

1. 2.1.c. 文字列の連結と置換

  • ここでは文字列の操作をおさえる

1.1. 練習問題

1.1.1. 問題と答え

----準備----
$ a=私は
$ b=俳優よ
----ここから答え----
$ c=$a$b; echo $c
私は俳優よ

$ a+=$b; echo $a
私は俳優よ

$ b=${a:0:1}${a:2:2}; echo $b
私俳優

$ c=${a/俳優/排骨麺}; echo $c
私は排骨麺よ

1.1.2. 補足

  • 連結したいとき
    • $ c=$a$b; で並べればOK
  • 文字列追加したいとき
    • $ a+=$b; echo $a で足し込めばOK
  • 文字列取り出したいとき
    • ${変数名:開始位置:長さ}で取り出せる。
  • 置換したいとき
    • ${変数名/置換対象文字列/置換後の文字列}でOK

シェルワンライナー #2.1.b. シェルと変数

1. 2.1.b. シェルと変数

  • ここでは変数の基礎をおさえる
  • Bashで変数を定義するときは変数名=値となる文字列と記述する。
  • コマンド入力する際使いたい位置に $変数名** あるいは ${変数名}と書くと値の文字列に置き換わる

1.1. 練習問題

1.1.1. 問題

Bashが持っている変数SHELLを表示

1.1.2. 答え

$ echo $SHELL
/bin/bash

1.1.3. 補足

  • =の前後に空白はNG
    • だめな例 $a = ほげほげ
    • aがコマンドで=ほげほげが引数と解釈しちゃうらしい

VSCodeでMarkdownを書きやすくするためにやったこと

VSCodeMarkdownを書きやすくするためにやったこと

インストールした拡張

設定した項目

  • Markdownのデフォルトフォーマッターをmarkdownlintに設定
  • Markdownでも入力予測が有効化
    "[markdown]":{
        "editor.unicodeHighlight.ambiguousCharacters": false,
        "editor.unicodeHighlight.invisibleCharacters": false,
        "diffEditor.ignoreTrimWhitespace": false,
        "editor.wordWrap": "on",
        "editor.quickSuggestions": {
            "comments": "on",
            "strings": "on",
            "other": "on"
        },
        "editor.defaultFormatter": "DavidAnson.vscode-markdownlint",
    }
}

その他

  • setting.jsonの場所(Windows
    • %APPDATA%\Code\User\settings.json

シェルワンライナー #2-1a (標準入出力について解説)

はじめに

  • ここから2章スタート
  • bashについて細かく説明してくれるらしい。
  • 2-1では、標準入出力とシェルの文法の基礎を説明してくれるらしい。

理解したことをメモ

  • コマンド結果をファイルに出力するリダイレクトは ls > a と書けるし、ls 1> a とも書ける。
  • ファイルの中身をコマンドとして入力するリダイレクトはcat | ... の他に < or 0< とも書ける。
  • sedを実行したときの説明文をaというファイルに保存したいときは?
    • 単純に $ sed < a だとできない。
    • なぜならsedを実行したときに出てくる説明文は標準エラー出力だから。
    • $ sed 2< a とするとできる。
  • 基本コマンドは渡すべきデータを 標準出力 、そうでないデータを 標準エラー 出力で区別している。
    • リダイレクトとかパイプで渡されるのは 標準出力 。だから$sed < a じゃあかん。
  • コマンドがデータを読むのは 標準入力
    • 標準入力はキーボードにつながっている。
  • 0< , 1> , 2>ファイル記述子(ファイルディスクリプタ という
  • 0: 標準入力 ,1: 標準出力, 2: 標準エラー出力, を表す。
  • コマンドが独自にファイルを開いてデータを読み書きするときは3以上の番号にファイルへの接続に割り当てられる。
  • ちな、sedの説明文をコマンドに渡したいときは...
    • $ sed 2>&1 | wc -l でいける
      • n>&m は n番をm番がつながっている先に振り分ける。という意味
      • 標準エラー出力を標準出力につないで、それをパイプを使ってwcコマンドに流している
      • パイプは標準出力を次のコマンドに渡せるので、これで、標準エラー出力を次のコマンドへ流せるようになる。
  • lessコマンドはパイプで受けたデータを上下して読める。(manコマンド読むときみたいな。)
    • こういうコマンド(溜まった出力をユーザがゆっくり読める系)を ページャ という。

シェルワンライナー #11

答え

cat gijiroku.txt | xargs -n2 | sed 's/^すず/鈴木/;s/^さと/佐藤/;s/^やま/山田/;s/ /:/;s/$/\n/;'

詳細

xargsコマンドの-n2オプションについて

xargsコマンドは、標準入力から受け取ったデータを、引数として他のコマンドに渡すために使用される。-nオプションは、xargsが一度に渡す引数の最大数を指定するために使用される。

-n2オプションは、xargsが他のコマンドに一度に渡す引数の最大数を2に制限することを意味する。これにより、xargsが一度に処理するアイテムの数が2つに制限される。

例えば、以下のようなコマンドがある。

echo "1 2 3 4 5 6" | xargs -n2

このコマンドは、次のような出力を生成する。

1 2
3 4
5 6

echoコマンドで1 2 3 4 5 6という文字列を出力し、それをxargs -n2に渡している。 xargsは、一度に2つのアイテムを処理するように指定されているため、入力文字列を2つのアイテムごとに分割し、それぞれのペアを別々の行に表示する。

-n2オプションは、例えば一度に処理できるアイテム数に制限があるコマンドを実行する際に役立つ。これにより、xargsを使って効率的にデータを処理できるようになる。

sed$について

sedコマンド内の$は、行の終わりを示す特殊なメタ文字である。これを使用して、行末にマッチさせることができる。

例えば、s/$/\n/コマンドは、各行の終わりに改行文字を挿入する操作を行う。

シェルワンライナー #10

Markdownファイルの見出し書式を変更するコマンドについて

Markdownファイルの見出し書式を変更するために、以下のコマンドを使用することができます。

cat headings.md | sed -r 's/^## +(.)/\1\n---/' | sed -r 's/^# +(.)/\1\n===/'

コマンドの解説

このコマンドは、headings.mdというMarkdownファイルの見出し(ヘッダー)の書式を変更しています。具体的には、次のような処理を行っています。

  1. cat headings.mdheadings.mdファイルの内容を表示します。
  2. sed -r 's/^## +(.*)/\1\n---/'sedコマンドを使って、二つ目の見出し(##で始まる行)を変更します。具体的には、##の後に1つ以上のスペースと任意の文字列が続く行(^## +(.*))を、その文字列に改行と---を続けたものに置き換えます(\1\n---)。
  3. sed -r 's/^# +(.*)/\1\n===/':同様に、sedコマンドを使って、一つ目の見出し(#で始まる行)を変更します。具体的には、#の後に1つ以上のスペースと任意の文字列が続く行(^# +(.*))を、その文字列に改行と===を続けたものに置き換えます(\1\n===)。

正規表現の解説

キャプチャグループ

\1は、正規表現でキャプチャした最初のグループにマッチした文字列を表します。ここでは、正規表現の中で括弧(.*)を使ってキャプチャグループを作成しています。

括弧(.*)を使ってキャプチャグループを作成し、マッチした部分を後で参照できるようになります。.は任意の1文字にマッチし、*は直前の文字が0回以上繰り返されることを意味します。従って、.*は任意の長さの文字列(1文字以上)にマッチします。

一つ以上のスペース

+正規表現において、直前の文字やグループが1回以上繰り返されることを意味します。今回の例では、###の後に1つ以上のスペースがあることを想定しています。そのため、+を使って1回以上のスペースをフィルタリングしています。

このコマンドを実行すると、headings.mdファイルの内容は次のように変更されます。


別件

Q1

下記要件を満たす処理をc言語で記述してください。

要件

  • ハンドルを回すとインクリメントされるカウンタがある。
  • ハンドルを逆方向に回すとデクリメントされる。
  • カウンタの取りうる値は0-255。

  • 1ms周期タスク上で実行する処理を考える。

    • その処理では、毎周期カウンタ値を取得する
    • 前回周期でのカウンタ取得値と、現在のカウンタ取得値を比較する
    • 比較した結果、カウンタが増えている場合、モードをONにする。
    • 比較した結果、カウンタが減っている場合、モードをOFFにする。

A1

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

// カウンタの値を格納する変数
static uint8_t counter = 0;

// ハンドルを回すとインクリメントされる関数
void handle_turn_clockwise() {
    if (counter < 255) {
        counter++;
    }
}

// ハンドルを逆方向に回すとデクリメントされる関数
void handle_turn_counterclockwise() {
    if (counter > 0) {
        counter--;
    }
}

// カウンタ値を取得する関数
uint8_t get_counter_value() {
    return counter;
}

// 1ms周期タスク
void one_ms_task() {
    static uint8_t prev_counter_value = 0;
    bool mode = false;

    uint8_t current_counter_value = get_counter_value();

    if (current_counter_value > prev_counter_value) {
        mode = true; // モードをONにする
    } else if (current_counter_value < prev_counter_value) {
        mode = false; // モードをOFFにする
    }

    prev_counter_value = current_counter_value;

    // ここでモードを使用して何らかの処理を行う
    // ...
}

int main() {
    // ハンドルを回す操作や逆方向に回す操作をシミュレート
    handle_turn_clockwise();
    handle_turn_clockwise();
    handle_turn_counterclockwise();

    // 1ms周期タスクを呼び出す(実際のアプリケーションではタイマー割り込みなどで実行する)
    one_ms_task();

    return 0;
}

このコード例では、カウンタの値をインクリメント・デクリメントする関数と、カウンタ値を取得する関数が定義されています。また、1ms周期タスク内で、前回のカウンタ値と現在のカウンタ値を比較し、モードをON/OFFに変更しています。実際のアプリケーションでは、1ms周期タスクはタイマー割り込みなどを使用して実行する必要があります。

Q2

追加要件

  • ハンドルを順方向に回すと、カウンタがmax値(255)までいったとき、カウンタ値は255→0に変わる。

    • このときカウンタが減ったためハンドルが逆方向に回されたと認識してしまう。
    • 実際にはハンドルを順方向に回している。
    • この問題を解決したい。
  • ハンドルを逆方向に回すと、カウンタがmin値(0)までいったとき、カウンタ値は0→255に変わる。

    • このときカウンタが増えたためハンドルが順方向に回されたと認識してしまう。
    • 実際にはハンドルを逆方向に回している。
    • この問題を解決したい。
  • 1カウンタ値は必ずしも1周期で1ずつインクリメントorデクリメントされるのではない。

    • 例えば252→ 4になったり、252→0になったりする。
    • この場合にも対応できるようにしたい。

A2

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

// カウンタの値を格納する変数
static uint8_t counter = 0;

// ハンドルを回すとインクリメントされる関数
void handle_turn_clockwise() {
    counter++;
}

// ハンドルを逆方向に回すとデクリメントされる関数
void handle_turn_counterclockwise() {
    counter--;
}

// カウンタ値を取得する関数
uint8_t get_counter_value() {
    return counter;
}

// 1ms周期タスク
void one_ms_task() {
    static uint8_t prev_counter_value = 0;
    bool mode = false;

    uint8_t current_counter_value = get_counter_value();
    int16_t diff = (int16_t)current_counter_value - (int16_t)prev_counter_value;

    // 周期内に複数インクリメント/デクリメントされた場合の誤差を許容する範囲
    int16_t threshold = 127;

    if (diff > 0 && diff <= threshold) {
        mode = true; // モードをONにする
    } else if (diff < 0 && diff >= -threshold) {
        mode = false; // モードをOFFにする
    } else if (diff > threshold) {
        mode = false; // カウンタがmin値(0)からmax値(255)に戻った場合
    } else if (diff < -threshold) {
        mode = true; // カウンタがmax値(255)からmin値(0)に戻った場合
    }

    prev_counter_value = current_counter_value;

    // ここでモードを使用して何らかの処理を行う
    // ...
}

int main() {
    // ハンドルを回す操作や逆方向に回す操作をシミュレート
    handle_turn_clockwise();
    handle_turn_clockwise();
    handle_turn_counterclockwise();

    // 1ms周期タスクを呼び出す(実際のアプリケーションではタイマー割り込みなどで実行する)
    one_ms_task();

    return 0;
}
  • このコードでは、1ms周期タスク内で前回のカウンタ値と現在のカウンタ値の差(diff)を計算しています。
  • カウンタ値の差が正で閾値(threshold)以下の場合、モードをONに設定し、カウンタ値の差が負で閾値以上の場合、モードをOFFに設定します。
  • また、カウンタ値の差が閾値より大きい場合や閾値より小さい場合は、カウンタがmax値(255)からmin値(0)に戻った場合やmin値(0)からmax値(255)に戻った場合として、それぞれモードを適切に設定します。

  • このようにすることで、1周期でカウンタ値が複数インクリメント/デクリメントされる場合にも対応できます。

  • ただし、閾値は適切に設定する必要があります。
  • 閾値が大きすぎると、誤差が大きくなりますし、閾値が小さすぎると、カウンタ値が複数インクリメント/デクリメントされた場合に正しくモードを設定できません。
  • 実際のアプリケーションでは、閾値を適切に設定することが重要です。

Q3

今回は基本的には4ずつインクリメント or 4ずつデクリメントされます。 どのようなしきい値が適切でしょうか。

A3

基本的にカウンタが4ずつインクリメントまたはデクリメントされる場合、しきい値はカウンタの変化範囲を考慮して設定する必要があります。ここで、最大の変化量は4であり、1周期内でカウンタが複数のインクリメント/デクリメントを行うことを許容する範囲を示すため、しきい値を4よりも大きく設定することが適切です。

例えば、しきい値を8と設定すると、1周期内で最大2回のインクリメント/デクリメントが許容されることになります。このように、しきい値を適切に設定することで、カウンタが4ずつインクリメント/デクリメントされる場合でも、正しくモードを判断できます。

以下のコード例では、しきい値を8に設定しています。

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

// カウンタの値を格納する変数
static uint8_t counter = 0;

// ハンドルを回すとインクリメントされる関数
void handle_turn_clockwise() {
    counter += 4;
}

// ハンドルを逆方向に回すとデクリメントされる関数
void handle_turn_counterclockwise() {
    counter -= 4;
}

// カウンタ値を取得する関数
uint8_t get_counter_value() {
    return counter;
}

// 1ms周期タスク
void one_ms_task() {
    static uint8_t prev_counter_value = 0;
    bool mode = false;

    uint8_t current_counter_value = get_counter_value();
    int16_t diff = (int16_t)current_counter_value - (int16_t)prev_counter_value;

    // 周期内に複数インクリメント/デクリメントされた場合の誤差を許容する範囲
    int16_t threshold = 8;

    if (diff > 0 && diff <= threshold) {
        mode = true; // モードをONにする
    } else if (diff < 0 && diff >= -threshold) {
        mode = false; // モードをOFFにする
    } else if (diff > threshold) {
        mode = false; // カウンタがmin値(0)からmax値(255)に戻った場合
    } else if (diff < -threshold) {
        mode = true; // カウンタがmax値(255)からmin値(0)に戻った場合
    }

    prev_counter_value = current_counter_value;

    // ここでモードを使用して何らかの処理を行う
    // ...
}

int main() {
    // ハンドルを回す操作や逆方向に回す操作をシミュレート
    handle_turn_clockwise();
    handle_turn_clockwise();
    handle_turn_counterclockwise();

    // 1ms周期タスクを呼び出す(実際のアプリケーションではタイマー割り込みなどで実行する)
    one_ms_task();

    return 0;
}

このコードでは、閾値が8に設定されているため、カウンタが4ずつインクリメント/デクリメントされる場合でも正しくモードを判断できます。ただし、閾値を適切に設定することが重要です。カウンタの変化範囲やアプリケーションの要件に応じて、適切な閾値を選択してください。