IT系エンジニア

2021/10/25 18:02 その他::メモ

エンジニア系


IT 系エンジニアがどのような職種なのか、簡単に説明する資料を作ってみました。

インフラエンジニアから見る IT 業界
インフラエンジニアから見る IT 業界

以下で、それぞれの枠について説明したいと思います。

システム開発

システムエンジニアを大雑把にいえば、システムの組み合わせと調整を主に行います。
小さな運用スクリプトやマクロ等は作ることがありますが、大規模なアプリケーション開発は行いません*1

システム開発において「要件定義」「設計」「構築」「検証」といったフェーズで、それぞれの成果物を要求されます。

要件定義は、お客様から聞いた内容を要件として整理するフェーズです。
どんな要求があり、何を使って解決するのか、システムの大枠を整理します。
プロジェクトによっては、コンサルタントが既に済ませていることもあります。
アウトプットとして、要件定義書が求められます。

設計は、要件定義を具体的に落とし込むフェーズです。
細かい設定値はさておいて、要件に沿った方針を決める基本設計と、基本設計をもとに設定値に落とし込む詳細設計に分けることが多いです。
アウトプットとしては設計書(基本設計書/詳細設計書)になるのですが、実際に構築するための作業手順書が含められることも多いです。

構築は、設計値を元に、実際にシステムをくみ上げていくフェーズです。
作業手順書をもとに作業するため、アウトプットとして、いつ、手順書のどこを、どのように実行したかを示すエビデンスと、第三者が設定値を検証可能なエビデンスを求められます。
よく利用されるのは、ツーマンセルで一人が実行役、一人が記録役になります。記録役が、印刷された手順書に実行時刻を記載することが多い *2 です。

専用のアプリを作るまでもない、運用のためのスクリプトを作ることもあります。
最低限、フローチャートは読めないと困るでしょう。

システムで利用する機器とそれに付属する OS /専用アプリはもちろん、アプライアンス(システム用の製品)関係の知識も必要です。

非常に幅広い知識と応用力などが要求されます。

なお、システムエンジニアは基本的にプレイヤーなので、マネジメントも行うようになるとマネジメントプレイヤーもしくは何でも屋という名称で呼ばれたりします。
インフラエンジニア
システムエンジニア内でも専門性があり、次のように分かれます。
サーバエンジニア
コンピュータでサービスを動かし、システムに必要なサーバを提供するのが主な仕事になります。

サーバの物理的なスペックから載せる OS についての知識、サービス用のアプリケーションインストール、状況に拠ってはチューニングといった仕事を行います。

サーバには UNIX や Linux といった POSIX 準拠の OS 上で動くものや、Windows で動くものなどがあります。
Linux しか触れない、Windows しか触れないといったエンジニアもいます。

POSIX で共通仕様は決められていてもベンダーごとに特徴はあるので、HP-UX は使えても Solaris は分からないといったことはあり得ます。
ネットワークエンジニア
ネットワークケーブルを敷設し、お客様とサーバが円滑に通信できるネットワークを提供するのが主な仕事になります。
実績と信頼性の高さから、商業用には主に Cisco 製品の知識が求められます。
普段は bps (bit per sec) の単位でネットワークを見ているので、Byte 単位が基本のアプリ側とやり取りするときは変換が必須です。
データベースエンジニア
システムが利用する情報を整理し、適切に応答するデータベースを提供するのが主な仕事になります。
商業用には主に Oracle 製品の知識があると良いでしょう*3

OSS としては mariaDB が採用されることも多いです。
SQL という、DB 向けの言語であってもアプリ毎の設計方針から方言の癖がつよいので、この DB しか使えないという DB エンジニアもいます。
セキュリティエンジニア
システムに含まれる脆弱性と、無数のアタッカーからシステムを守るシステムを提供するのが主な仕事になります。
ネットワーク、OS、アプリそれぞれのセキュリティ情報や、防御用アプライアンスの知識などが求められます。
ソフトウェアエンジニア
ソフトウェエンジニアもシステムエンジニアに数えられますが、ここでは割愛して別項目にします。
ウェブエンジニア(未掲載)
ウェブエンジニアもシステムエンジニアに数えられます。
ただし、ウェブ上で利用されるシステムという、分かるような分からないような曖昧な定義になっています。

共通するのは、WebUI *4 をもつシステムに携わるというところです。

WebUI のデザインを行っているかと思えばネットワークのボトルネックを調べていたり、アプリケーション開発をしていたり、サーバのチューニングをしていたりします。

扱う範囲が広すぎるので、お客様が直接扱うソフトウェア側をフロントエンド、フロントエンドを構成するためのインフラ側をバックエンドと呼ぶようです。

上の図には未掲載です。
フロントエンドエンジニア
ブラウザで直接操作するアプリと、その UI を主に扱うエンジニアを、フロントエンドエンジニアと呼びます。
お客様の前に立つ、ホテルのフロントをイメージすると理解しやすいかもしれません。

HTML / CSS / XML / JSON といった、ブラウザで直接扱うマークアップ言語や、そのコードを出力するためのアプリケーションに Java / Ruby / PHP といったスクリプト(インタプリタ型言語)が使われています。

まれに、高速な応答が必要なシステムで C 言語といったコンパイラ言語(コンパイル型言語)が使われることもあるようです。

担当する業務によっては、ネットワークを意識することがないとか、他にもウェブデザイナー業務を担当することがあると聞いています。

バックエンドエンジニア
ブラウザ操作で意識しない、裏方のシステムを主に扱うエンジニアを、バックエンドエンジニアと呼びます。

インフラエンジニアと業務内容が被るのですが、インフラエンジニアはウェブ操作とは全く関係ないシステムも扱うため、微妙な差異で使い分けられています。
クラウドエンジニア(未掲載)
IaaS を中心としたサービスを主に扱うシステムエンジニアです。

コーポレートエンジニア

どちらかというと発注側のエンジニアで、社内システムに特化しているのがコーポレートエンジニアです。
社内エンジニアとも呼ばれます。

外注のために浅くても広い知識や、査収といった業務を行います。
このため、会社の業務知識だったり、他社への発注手続きだったりと、庶務系に比重が置かれることも多くなります。

また、次のツイートが参考になると思います。
言及のある、タスクディクショナリは、こちら。
https://icd.ipa.go.jp/icd/icd

コンサルタント

コンサルタントは、経営者やプロジェクトの責任者などが抱える課題について、アドバイスやソリューションを提供するのが主な仕事になります。
コンサルタントも専門で分かれるのですが、システムエンジニアからすると上流の仕事になります。

提案書を求められることがあります。
プリセールスエンジニア
プリセールスエンジニアはインフラエンジニアのなかでも営業に近く、システムの知識を持ちつつ、知識を生かした提案や、経営課題のソリューションを提供するのが主な仕事になります。
システムを見ながら、コンサルティングできるエンジニアです。
提案力(プレゼン能力)やコミュニケーションといった、政治力が強く求められます。

ワーカー

出来上がっている手順書の通りに、システムを構築したり検証したりといった作業を行います。
設計がおかしければ指摘したりする必要はありますが、基本的には事前にやることが決まっています。
そのため、エンジニアとは呼ばれません。

組込エンジニア

システムとしては少し毛色が変わりますが、組み込みエンジニアもエンジニアです。
サービスが動くのではなく、マイコンなどのデバイスで動くシステムを作ります。

どちらかというと、デバイスを制御するためのシステムですね。
ハードウェアの知識が必要になるため、デバイスの開発を行うこともあるようです。

コンピュータを内蔵する機械であれば組込システムが存在するため、目立ちませんが必要な役割です。
デバイス
組込エンジニアでも、身近な製品(デバイス)を開発するのがメインのエンジニアです。

IC やら抵抗やら、基盤設計を行う人がいると思えば、出来上がった基盤を制御するソフトウェア開発を行う人もいます。
もちろん、全部面倒みる人もいます。

マイコン(マイクロコンピュータ)や、組込 Linux もあって、外部からはかなり混沌とした業界のように見えます。
制御ソフト
ミシンや自動車といった身近なものから、工業ロボットのような大型の機械を制御するソフトウェア開発も、組込エンジニアのようです。

*1 : アプリケーション開発は、ソフトウェアエンジニアの仕事です。

*2 : PCでの時間入力は、改ざんが容易なため敬遠される傾向があります。

*3 : これを書いている時点では Oracle 社の経営方針に嫌気がさし、OSS に移行するユーザも増えています。

*4 : WebUI は、ウェブブラウザによりユーザがシステムを操作するインターフェースのこと。

ステークホルダー

アプリ開発

ここでは、システムエンジニア視点でのステークホルダーとしてくくっています。
デベロッパ
開発する人たちの総称です。

BIND、Apache/nginx、Sendmail、Samba、Docker/CRI-Oなどサーバとして動かすアプリを開発する人たちが含まれます。
彼らがいなければ、システムエンジニアは成り立ちません。

アプリに対する深い理解が必要不可欠です。
アプライアンス
特定目的のアプリを、想定されるユースケースにおいて使いやすいシステムで提供するベンダーです。

条件によっては採用するほうがコストが安く品質を上げられるため、彼らが世に出す製品は広く知っておくべきです。
あまり詳細を知れないので、雲の向こう側として表現しています。
その他
ソフトウェアエンジニアという呼称が存在する、立派なエンジニアです。
こちらもシステム開発動揺、要件定義、設計、コーディング、検証といった流れが存在します。
Web アプリを開発するなら、Web エンジニアと呼ばれたりするようです。

書いている人がソフトウェアエンジニアではないので外から眺めた感じの表現になってしまいますが、概ね間違っていないと思います。
ソフトウェアエンジニア
要件定義から検証まで、一貫して行えるアプリケーション開発者がソフトウェアエンジニアだと考えています。
プログラマ
要件定義の済んでいるアプリケーションを、設計から行うのがプログラマだと考えています。
コーダ
コーダは設計済みのフローチャート通りにコーディングする人のことだと考えています。
システムエンジニアにおけるワーカーのようなものですね。
テスタ
開発の済んだコードが、要件を満たす挙動をするかどうか試験するのがテスタだと考えています。
デバッガ
ほぼほぼテスタなのですが、開発の済んだコードの検証に加えて、開発者の想定外の挙動までチェックするのがデバッガのイメージです。

運用・保守

システムを作っても、それを維持したり、状況の変化に応じてメンテナンスしたりしなければ、動かないシステムになってしまいます。
基本的には想定される状況がすべて網羅された手順書があり、就職先としてのハードルは低いです。

問題が起きたら即座に対応しなければならないサービスの場合、システムエンジニアが兼任することもあります。
ヘルプデスク
クライアントの設定から故障対応まで、ユーザが操作するコンピュータを責任範囲とします。
理不尽が多いのも特徴です。
オペレータ
運用(オペレーション)を行います。

運用とは、たとえばユーザアカウントの追加や定期的な稼働確認といった内容になります。
24時間365日、システム監視ツールが動き、問題を検出したら状況確認から対応することになります。

対応は、関係部署への連絡です。

手順書はありますが、簡単に障害箇所を切り分ける能力が、ある程度求められます。

サーバが不通であればシステムエンジニアへ連絡。
ネットワークに問題があれば、ネットワークエンジニアに連絡。
物理機器が故障しているようであれば、保守員へ連絡。

そういった連絡が主な仕事です。
ただし、切り分けの結果によってはその場で対応しなければならないことも多く、運用と保守はひとまとめにされることも多いです。
保守員
保守(メンテナンス)を行います。
勤務時間は保守契約により日中対応だけのものから、24時間対応のものもあります。

保守とは機械が壊れたときに交換するといった内容になります。
基本的には連絡待ちになりますが、初期稼働を早めるためにシステム監視を行うことがあります。

内容的に運用と被るものが多いので、運用と保守はひとまとめにされやすいです。

管理

プロジェクトやプレイヤーのマネジメントを行う人たちです。
PM
PM はプロジェクトマネージャーの略で、プロジェクトを管理します。
管理とは、予定をたてたり、進捗状況をみてテコ入れをしたり、お客様と交渉したり、成果物を見て改善を指示したりといった内容です。
進捗管理や品質管理といった呼称が使われます。
PMO
PMO はプロジェクトマネジメントオフィスの略で、PM が担当するプロジェクトをチームとして支援します。

PM は一人ですが、PMO は複数人でプロジェクトにあたります。
大規模な開発では一人で進行を管理しきれないため、必要となります。
PL
PM の立てた計画を遂行する立場です。

場合によってはシステムエンジニアが兼任することもありますし、システムエンジニアに PL の能力を求める人もいますが、基本的に PL はマネジメント側になります。

発注

ビジネスオーナー
ここでは、プロジェクトを定義して、システムを発注するのがビジネスオーナーであると定義しています。
システムエンジニアが直接話す機会は少なく、雲の上の存在となっています。

コーディング規約案

2021/07/24 09:06 OS別::Linuxその他::シェル

シェルコマンド言語 コーディング規約案

いろいろな考え方があり、それぞれのプロジェクトで最適解が異なります。
規約のたたき台として、まずは作成しました。
参考の一助になれば幸いです。
ここでは、自分が使いやすい規約にしてみました。
臨機応変に変更してください。

Google Shell guide といったものも存在します。
https://google.github.io/styleguide/shellguide.html


プロジェクトにおけるシェルコマンド言語を利用したスクリプトは、視認性やメンテナンス性の向上を目的として、次の規約に則り作成すること。

汎用性の考慮

UNIX として共通の仕様である POSIX に規定された範囲でのコーディングとし、独自拡張は極力利用しないこと。

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html

また、必要があって利用する場合は、ヘッダに注意事項として記載すること。
[Tips]
たとえば、Linux の for 文で C 言語のような ( i=0 ; i < x ; i++ ) という形式を利用できます。これは便利なのですが bash の独自実装なので汎用性が下がります。

スクリプト

スクリプトは、次の要件を満たすこと。

ファイル名
ファイル名は、英数字 + 拡張子とする。
拡張子
拡張子は、シェルスクリプト単体で実行するものを .sh とする。
また、変数や関数を別途まとめたファイルについては .src とする。
[Tips]
csh / tcsh / fish / bash / dash といった、シェルを複数利用するようなプロジェクトでは、拡張子をそれぞれに合わせた方が良いと思います。
bash しか利用しないことが分かっている環境で、シバンにも bash 指定のみの場合は .sh でも不都合はないと思います。

.src は、内容として bash なのですが source コマンド (ビルトインでは . コマンド) で呼ぶことを意図しています。
実行権限
実行権限は、システムとして特に指定のない限り 610 とする。
[Tips]
権限を 755 や 700 で運用することは多いのですが、セキュリティを考慮すると、権限は最小限にすべきでしょう。
ここでは本番環境を想定していて、作成するユーザと、実行するユーザが異なることを考慮しています。
所有者は、更新できるが実行できない。前提条件を満たさない、不用意な実行を阻止できる。
グループ(実行者)は、更新できないが実行できる。JP1 のような自動実行ユーザのスクリプト改変を阻止できる。

ファイル内容

構成
スクリプトは、次の構成を基本とする。
段落内容備考
ヘッダシバンやスクリプトの目的などを記載する。
変数宣言スクリプトが利用する変数を宣言もしくは指定する。source (.) での指定も可
関数宣言スクリプトが利用する関数を宣言する。source (.) での指定も可
メイン処理スクリプトが目的とする処理を行う。
終了処理一時利用のために展開していたテンポラリファイルの削除などを行う。

文字コード
文字コードは UTF-8 とする。
また、BOM は付与しないこと。
日本語はコメントに限定する。
[Tips]
コメントに日本語を使わない場合は ASCII を指定してもよいです。
むしろ ASCII として認識されるでしょう。
改行コード
改行コードは LF とする。
[Tips]
Windows でスクリプトを書くと、改行コードは考慮から漏れがちです。
Linux や UNIX 上で書くと気にしなくても良いのですが、プロジェクトの進行状況や管理方法次第で、どうしても Windows 端末での開発を行わなければならない状況もあります。
致命的ではない文字コードやインデントも指定するのですから、致命的エラーとなる改行コードは指定しておきましょう。
インデント
インデントは、半角スペース2つとする。
[Tips]
タブ (半角スペース4つもしくは8つ) だと、横に広がりすぎて視認性が悪くなることがあります。
シバン
シバンは、以下の通り bash とする。
#!/bin/bash
[Tips]
シバンとヘッダは、ひとまとめにしてしまっても良いかもしれません。
もしくは、拡張子の指定でシバンも固定することになるでしょう。
ヘッダ
スクリプトの冒頭には、スクリプトの概要を記載する。
項目内容備考
スクリプト名Script nameファイル名称を記載する。
変更Modify変更日・変更者・変更内容を記録する。

以下は、定義する際のサンプル。
項目内容備考
スクリプト名Script nameファイル名称を記載する。
目的Purposeスクリプトの目的を記載する。
引数Option指定可能なオプションを記載する。
変更Modify作成日を記録する。日付と担当者をひとまとめにしてもよい。
作成者Auther作成者を記載する。
更新日Update更新日を記載する。更新内容も併記するとなおよい。
権限Permission実行者や権限を記載する。
サンプル 1
# Script name: example.sh
サンプル 2
# スクリプト名: example.sh
# 目的: スクリプトサンプル
コメント
適宜コメントを記載すること。
コメント内は日本語で記述する。

サンプル
# システム

パラメータ関係

変数名
変数名は、別途定めるプレフィックス/サフィックスを含め、キャメルケースで指定する。
一部の予約された変数名以外は、単語の組み合わせで指定すること。
[Tips]
私がスネークケースよりキャメルケースのほうが読みやすいと感じるタイプなので、キャメルケースを指定しています。
メリット・デメリットを考慮して、規定するのが良いです。

変数名はスクリプト上部にまとめて宣言しておくので、エディタの画面分割機能でコピペすることが前提としてあります。
画面分割は vim でも可能です。
変数名が短くても、何回も手入力していればタイポする可能性は高いです。
プレフィックス
プレフィックスは、変数の用途に応じて次の文字を使用する。
文字用途備考
p汎用パラメータParameter を意図
n数値用パラメータNumber を意図
f0 を真としたフラグ情報Flag を意図
tループで頻繁に書き換える使い捨てパラメータTemporary を意図
iループ用に予約Integer を意図 (while/until での数値ループに使用)
sループ用に予約String を意図(for での文字列ループに使用)
サフィックス
サフィックスは、特定の用途で次の文字列を使用する。
文字用途備考
Path絶対パスでファイル情報を指定する場合に利用
Dirディレクトリ情報の場合に利用文字列の最後は / で終わること。
Nameファイル情報の場合に利用ディレクトリは含めないこと。
Enableフラグ管理など、0 で有効とする場合に利用
サフィックスとしての Disable は、Enable と併用すると混乱を招くことがあるので禁止する。
利用時の指定方法
変数を利用する場合は、ブラケットで括ること。
Exit Status
終了ステータスは、スクリプトの目的が満たされることを正常とし、正常終了した場合に 0 とする。

関数関係

関数は、
関数名
関数名は、別途定めるプレフィックスを含め、キャメルケースで指定する。
一部の予約された関数名以外は、単語の組み合わせで指定すること。
プレフィックス
プレフィックスは、変数の用途に応じて次の文字を使用する。
文字用途備考
fn関数 (ファンクション) であることを示す
テンプレート関数
fnLog
特定のログファイルに、毎回リダイレクトを記述するのは可視性としても悪くなり、ミスも誘発しやすい。
引数としてメッセージをログファイルへ書き込む関数を用意した。

スクリプトの進捗状況を記述する目的で利用すること。

サンプル
fnLog "Sample message"
fnDebug
開発時に利用したデバッグ用メッセージを、メンテナンス時にも再利用できるよう、フラグに応じて出力指定もできる関数を用意した。

デバッグ情報を記述する目的で利用すること。

サンプル
fnDebug "Debug message"
fnErr
シェルスクリプトが標準エラー出力を行うための関数を用意した。

エラー出力を記述する目的で利用すること。

サンプル
fnErr "Standard Error message"
fnErrEnd
存在しなければ即時で終了するための関数を用意した。

引数として、エラー番号と、対象のファイルまたはディレクトリなどを指定すること。

サンプル
[ -d "/etc" ] || fnErrEnd 14 "/etc"
[ -f "/etc/hosts" ] || fnErrEnd 15 "/etc/hosts"
[ -x "/bin/ps" ] || fnErrEnd 13 "/bin/ps"

メイン

オプション解析
getopts を利用した解析を行う。
[Tips]
    • help といったロングオプションを使いたい場合は、getopts ではなく、引数そのものを case で解析するほうがよいと思います。

備考

テンプレート
シェルスクリプト
サンプル
#!/bin/bash
############################################################
# Script name: example.sh
# Modify     : yyyy.mm.dd 名前 初版作成
############################################################

### Parameter and Variable #################################
pCommon=./src/common.src
if [ -f "${pCommon}" ]; then
  . ${pCommon}
else
  echo "${pCommon} not exist."
  exit 1
fi

### Function ###############################################
fnEnd() {
  # スクリプト内で異常終了する際の Exit Status と、メッセージ内容の定義
  nExitStatus=$1
  case "${nExitStatus}" in
     0 ) fnLog "Script ${0##*/} end.";;
     * ) fnErr "Unknown error ($2)"; nExitStatus=1;;
  esac
  exit ${nExitStatus}
}

### Main ###################################################
fnLog "Script ${0##*/} start."

## オプション解析
while getopts h OPT; do
  case ${OPT} in
    h )
      fnDebug "Option h used."
      echo "Usage ${0##*/} [-h]"
      echo " [-h]    help messages."
      fnEnd 0
      ;;
    * )
      fnDebug "Unknown option used."
      fnErrEnd 10 "${OPT}"
      ;;
  esac
done

# xxx 処理


### End ####################################################
fnEnd 0

############################################################
外部ファイル
サンプル
# 共通ファイル (common.src)

### Parameter and Variable #################################

## ディレクトリ
pLogDir="/var/log/script/"

## ファイル
pLogName=$(basename ${0%.*}.log)

## パス
# ログファイル
pLogPath="${pLogDir}${pLogName}"

# ヌルクリア
i=""
s=""

# Exit Status
nExitStatus=0

## フラグ
fDebugEnable=0

### Function ###############################################

# 目的:ログ出力
fnLog() {
  echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogPath}
}

# 目的:デバッグ出力
fnLog() {
  [ "${fDebugEnable}" -eq "0" ] && fnLog "[Debug] $*"
}

# 目的:エラー出力
fnErr() {
  # ログ出力
  fnLog "$*"
  # 標準エラー出力
  echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >&2
}

# 目的: エラー終了
fnErrEnd() {
  nExitStatus=${1}

  # 引数確認
  if [ -z "${nExitStatus}" ]; then
    fnErr "Not argument."
    exit 1
  fi

  case "${nExitStatus}" in
     0 ) fnErr "Function (fnErrEnd) argument error."; nExitStatus=1;;
    10 ) fnErr "Argument error ($2)";;
    11 ) fnErr "Read permission not exist ($2)";;
    12 ) fnErr "Write permission not exist ($2)";;
    13 ) fnErr "Exec permission not exist ($2)";;
    14 ) fnErr "Directory not exist ($2)";;
    15 ) fnErr "File not exist ($2)";;
     * ) fnErr "Unknown error ($2)";;
  esac
  exit ${nExitStatus}
}
||

実行中スクリプトの編集

実行中スクリプトの編集



まず、スクリプトの実行についておさらいしておきます。

スクリプトは、テキストを読んで命令文を逐次解釈し、記述された順番でコマンドを実行するものです。
これはコマンドインタープリタの標準的な挙動です。

そのため、シェルスクリプトも Windows バッチファイルも、実行中にファイルを書き換えたり、消したりするとエラー終了します。

検証方法

検証には、次のようなスクリプトを書くとよいでしょう。
#!/bin/bash
echo "Start"
sleep 30
echo "End"
sleep で待ち時間が発生している間に、End を書き換えたり、追記したり、ファイルを消したりすればよいわけです。

実際にやってみれば、書き換わった後に追記したコードが実行されたり、エラーが出たりと、予想できない挙動を示すでしょう。
そういった理由から誤って実行中にバージョンアップしてしまったり、自分自身を書き換えてしまうバグを作りこんでしまったときには、非常に困るわけです。

不意の修正に強くするためには

では、対策を考えてみます。

そもそもの仕様としてファイルへの読み込みが発生するので、実行中スクリプトの修正に書き換えを行わせないための策は、根本的にありません。

ですが、少しでも強くする方法はあります。
(1) 適切な権限を設定する
「書き込み権限があるので書き込めるのだ」という発想で、書き込み権限を消しておく方法です。

$ sudo chmod -w sample.sh

頻繁に修正の発生しない、もしくは発生しては困る本番環境では、考慮されているものと思われます。
デメリットとしては、修正を行う時は毎回権限を付与する、もしくは sudo を利用して書き換えるといった手間や権限の乱用に繋がる点でしょう。
(2) 書換を検知する
スクリプト自体に、自身の書き換えを検知させる方法としては、ls や file、stat といったコマンドで自身の更新時刻を管理させます。

頻繁に実行する内容になるでしょうから、関数として呼び出すのが自然でしょう。

Linux bash では、関数にすると環境変数としてセットされるようです。
何が環境変数としてセットされているかは set コマンドで確認できます。

環境変数はメモリに展開されるので、ファイル編集の影響をうけません。
ということは、関数はファイル編集だけならば影響をうけません。

環境変数を書き換える行為(関数の再定義)が必要です。

つまり、スクリプト始めにファイルの更新時刻を環境変数として持っておき、以後は適宜差分をみる方法がとれます。

差分自体は test コマンドで比較すれば、そう難しいロジックを組むことなく実装できるでしょう。


かなり荒々しいですが、次のようなサンプルでも差分検出としては要件を満たせるでしょう。

#!/bin/bash
before="$(ls -l $0)"

functionDiff() {
  after="$(ls -l $0)"
  [ "${before}" == "${after}" ] || echo "NG"
}

functionDiff
また、md5sum といった、ファイルの破損や編集を検知するためのチェックサム関係コマンドを利用するのも良いです。

(3) メモリに読み込む 1
関数はメモリに置かれた内容が実行されるので、影響を受けにくいことは前段で記述しました。
ならば、出来るだけ関数にしてしまえば影響を極力排除できるだろう、という発想です。

関数などは別ファイルに記述できるので、究極、制御系(判断分岐や関数実行)しかないスクリプトになります。
関数名に気を遣えば抽象的な構文になるので、かなり読みやすいスクリプトになると思います。

外部ファイル /tmp/example.conf

### example.conf

# Parameter or Variable
example="EXAMPLE"

# Function
exFunction() {
  echo "${example}"
}

実行ファイル /tmp/example.sh

#!/bin/bash
### Script name: example.sh
# Include
. ./example.conf

# Main
exFunction

(4) メモリに読み込む 2
スクリプトに記載されている内容を、一度メモリに読み込んでおくという手法も取れます。

実体としては、一度、実行したいスクリプトを変数に読み込んで、echo してあげて1行ずつ解釈していく方法になるでしょう。


実行したいスクリプト /tmp/example.sh
#!/bin/bash
# script name : example.sh
echo a
echo b
echo c

注意事項として bash の echo では、変数を展開するときダブルクォートがあるかどうかで、出力形式が変わります。
具体的には、次の通りです。

envbar=$(cat /tmp/example.sh)
$ echo ${envbar}
#!/bin/bash # script name : example.sh echo a echo b echo c
$
$
$ echo "${envbar}"
#!/bin/bash
# script name : example.sh
echo a
echo b
echo c

余談ですが、変数名のブラケット と は無くても大丈夫です。付けておくと、変数展開がしやすいのでお勧めです。
なお、シングルクォートでは変数展開が行われません。

サンプルとしては、次の通りです。

example.sh を実行するコマンド

envbar=$(cat /tmp/example.sh)
$ echo "${envbar}" | bash -
a
b
c

まとめ

実行中の書き換えに対する予防策は、アクセス権限だったり検知機構だったり、ファイル内容の取得だったりといろいろ示しましたが、意図して起こした場合には無力です。

複数の防御手段を併用するのが、一番強力です。

感想

変数への読み込みを行うメリットは、メモリに展開される関係で処理速度が段違いに早くなることも考慮できるでしょう。
特に、HDD といった比較的遅い媒体からの読み出しとオンメモリでは、比較になりません。

変数は int (32bit) で定義されていることが多いようなので 4GB までは対応していそう*1です。

変数への取り込みは、スクリプト内で変数へ読み込んでおいて、1行ずつデータを解析していくにも良さそう。

技術の引き出しが多いことに越したことはないので、他にも方法があれば教えていただきたいです。

*1 : メモリに展開する以上、メモリの最大容量にも依存するものと思われる。