どっちなの問題

こんにちは、イメージマジック三浦です。
弊社本社がある小石川一帯は最近大きいビルの建設が終わり、テナントも入り始めてきました。工事はまだまだ継続中ですが、工事が終わったらどんな街になっているのか気になります。
最近解釈が分かれやすい表現は極力避けたいと感じた例を見かけたので、人によって解釈が分かれる、または分かれやすい例を「どっちなの問題」と名付けて書いてみます。

こんな数式を見かけました

いきなりですが、四則演算の問題です。以下の式を計算するといくつになるでしょうか。
6÷2(1+2)
回答は2通りに分かれるそうです。
説1:6÷2(1+2)=3×3=9
説2:6÷2(1+2)=6÷6=1

真実が実は2つ?

回答が2通りに分かれるポイントは、カッコの中を計算した後の掛け算と割り算をどういう順番で扱っているかです。「2(1+2)」は「2×(1+2)」の掛け算記号が省略されているのですが、省略される掛け算を扱う優先順位が異なることで答えが2説出てきます。
  • 説1:カッコの中を計算した後、左から順番に割り算と掛け算を実行する。
  • 説2:カッコの中を計算した後、省略されている掛け算を優先して実行し、その後で割り算を実行する。

プログラム言語での扱い方

コンピューターではこの数式をどう処理するのか調べてみましたが、そもそもプログラム言語では掛け算記号の省略表記ができませんでした。参考として、手元にあるpython3.7で省略表記しようとした時のTraceback・説1の結果を出す式表記・説2の結果を出す式表記を書いた結果を掲載します。
(結果が少数表記になる点は、ここでは考慮しないことにします。)
Python 3.7.1 (v3.7.1:260ec2c36a, Oct 20 2018, 14:57:15) [MSC v.1915 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> 6/2(1+2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
>>>
>>> 6/2*(1+2)
9.0
>>>
>>> (6/2)*(1+2)
9.0
>>>
>>> 6/(2*(1+2))
1.0
>>>
カッコをつけることで、掛け算・割り算の優先順位が明示されています。

正しい答えはどっち?

正しい答えは説2の方でした。実際に関数電卓で計算してみると、説2の答えが出てきます。
これは解釈が分かれやすい「どっちなの問題」です。

その他の「どっちなの問題」

今回のテーマから派生して、解釈が分かれる「どっちなの問題」の例を挙げます。

0の0乗はいくつ?

「xの0乗」は、0でない実数(-1のルートを考えなくてよい世界)では常に1ですが、0の0乗はどうなるのかは数学の中でも統一されていません。1と決めると都合がいい分野と「定義しない」と決めると都合がいい分野があり、各分野が都合のよい方を定義に採用して理論構築しています。

旧暦2033年問題

今使われている「グレゴリオ暦」のひとつ前「天保暦」の問題です。天保暦では2月・5月・8月・11月を設定し、設定後に他の月を設定します。しかし、100年に一回程、設定できる月が1つに定められないという事態が発生するケースがあり、直近では2033年~2034年に起こるというものです。
俳句で使う季語は旧暦の春夏秋冬で区分けされる等、決められないことによる影響は小さくないようです。こちらは一定のルールを決めて解決しようとしているようですが、解決方法が複数あって検討中のようです。

詰将棋【最後の審判】

詰将棋は、限られた自軍の駒を動かしながら相手の王将を詰ますパズルで、本将棋のルールに加えて詰将棋独自のルールがあります。本将棋のルールでは「禁じ手」が規定されていますが、「最後の審判」は詰将棋のルール上「禁じ手」を指すしかない状況が双方で生じた時に勝敗を判断できない、という状態が発生する作品です。
便宜上、詰将棋の1種として記載していますが、実は詰将棋として成立するかも結論が出ていません。 こちらについて興味のある方は、【詰将棋 最後の審判】で検索してみてください。

注:単に【最後の審判】で検索すると、ミケランジェロの「最後の審判」がヒットします。これも 「最後の審判」と言われて、絵なのか詰将棋なのかで解釈が分かれる「どっちなの問題」の一例です。

横がダメなら縦でやってみる

こんにちは。イメージマジック三浦です。うだるような暑さの毎日から一変し、過ごしやすい気候に変わってきました。寒暖の差が大きくなってきましたので、体調を崩さないように気を付けていきたいところです。 今回はSQLの集計クエリを書いていた時の話です。

やりたいこと

・同じ期間内でテーブルA,Bをそれぞれ日付と区分(区分はAとBの両方にある)で集計し、その結果セットを以下イメージの形式で取りたい。
 yyyy-mm-dd | 区分1の集計値 | 区分2の集計値 | 区分3の集計値 | …
ただし、テーブルAとBには以下の状態が想定されます。
  1. AとBの両方に含まれる日付がある
  2. Aだけに含まれる日付がある
  3. Bだけに含まれる日付がある

横ではできなかった理由

2の条件と3の条件が両立する可能性があったため、以下のように横方向にデータを結合していく方式では、どうしても集計漏れが出ます。
SELECT * FROM A INNER JOIN B ON A.日付=B.日付 WHERE …
SELECT * FROM A LEFT JOIN B ON A.日付=B.日付 WHERE …
SELECT * FROM A RIGHT JOIN B ON A.日付=B.日付 WHERE …
完全外部結合で対応しようとおもいきや、MySQLやmariaDBは完全外部結合が使えませんので、横方向へのデータ結合では要件を満たすことができません。
※これは使えない
SELECT * FROM A FULL OUTER JOIN B ON A.日付=B.日付 WHERE …

完全外部結合ができないわけではない

このようなクエリにより、完全外部結合を再現することはできます。
SELECT * FROM A LEFT  JOIN B ON A.日付=B.日付 WHERE …
UNION
SELECT * FROM A RIGHT JOIN B ON A.日付=B.日付 WHERE …
または
SELECT * FROM A LEFT JOIN B ON A.日付=B.日付 WHERE …
UNION
SELECT * FROM B LEFT JOIN A ON B.日付=A.日付 WHERE …
しかし、今回はAとBそれぞれに集計対象の条件が異なり、同じ集計条件を1クエリ内で2度書く必要がある分、クエリが複雑になります。スポット集計用ならまだしも、これからもずっと運用していく予定のクエリだったので、複雑になることを避けるために導入を見送りました。 しかし、UNIONを使う方針は有力でした。

UNIONを使って事前集計を行う

UNIONを使って縦方向の結合により集計値を取得します。 UNIONを使うためには、SELECT文のカラム数を同じにする必要があるので、カラム数合わせの0をセットします。後の集計のため、エイリアスもつけています。UNIONにより重複が排除される効果もあります。
SELECT 日付, 区分
, Aの集計値1, Aの集計値2, …
, 0 AS Bの集計値1, 0 AS Bの集計値2, …
FROM A
WHERE …
GROUP BY 日付, 区分
UNION
SELECT 日付, 区分
, 0 AS Aの集計値1, 0 AS Aの集計値2, …
, Bの集計値1, Bの集計値2, …
FROM B
WHERE …
GROUP BY 日付, 区分

本集計

予備集計のクエリをインラインビューとし、インラインビュー内の集計値をさらに集計します。ダミーのカラム値を0としたことで、SUMを実行した時に影響しないようになっています。
SELECT
日付
, SUM(CASE WHEN X.区分 = 1 THEN Xの集計値 ELSE 0 END) AS summary1
, SUM(CASE WHEN X.区分 = 2 THEN Xの集計値 ELSE 0 END) AS summary2
, SUM(CASE WHEN X.区分 = 3 THEN Xの集計値 ELSE 0 END) AS summary3
, SUM(CASE WHEN X.区分 = 4 THEN Xの集計値 ELSE 0 END) AS summary4
, …
FROM (
SELECT 日付, 区分
, Aの集計値1, Aの集計値2, …
, 0 AS Bの集計値1, 0 AS Bの集計値2, …
FROM A
WHERE …
GROUP BY 日付, 区分
UNION
SELECT 日付, 区分
, 0 AS Aの集計値1, 0 AS Aの集計値2, …
, Bの集計値1, Bの集計値2, …
FROM B
WHERE …
GROUP BY 日付, 区分
) X
GROUP BY X.日付
SUMの結果について、もれなくエイリアスを定義しておくことが最後のポイントです。これにより、プログラムロジック内で集計値を扱いやすくなります。

小話

「インラインビュー」という言葉を初めて聞いたのは、新卒2年目の時に入った案件で見た設計ドキュメントでした。当時は何も分からないながら、何とかしてクエリを書きましたが、そのクエリは廃棄されてしまったそうです。 おまけに、そのことを聞いたのは案件を外れて半年後でした。 派遣エンジニアだった頃の、1つの思い出です。

デザインに使える比率の話

皆さんこんにちは。イメージマジック三浦です。 新型コロナウイルスに対して、弊社でも在宅勤務等の感染予防対策が進んできて今までと違う状況が日常になりつつあります。一日も早い収束を願いつつも、日々自分にできることをやっていこうと思います。 今回は「デザインに使える比率の話」と題して、黄金比と白銀比を紹介します。

黄金比

1:(1+√5)/2≒1:1.6
(1+√5)/2は黄金数とも呼ばれ、フィボナッチ数列の一般項表記に出てくる数です。整数値で比率を表すと5:8となります。
Web画面を作る時、通常表示向けと強調表示向けのフォントサイズを5:8にすることがありますが、通常表示側が奇数になる場合は以下2つの内、1.6に近い方を使うようにしています。
  • 強調表示向け÷(通常表示向け+1)
  • 強調表示向け÷(通常表示向け-1)

白銀比

貴金属比による定義…1:(1+√2)≒1:2.4
直角二等辺三角形の斜辺と底辺の比…1:√2≒1:1.4
「1:√2」は紙の寸法に出てくる他、日本の建築物に多く取り入れられています。また、東京スカイツリーの「東京スカイツリー展望回廊」とタワー全体の高さが約1:1.41で、これも白銀比になっています。

少し数学的な話

第n貴金属比…1:{n+√(n^2)+4}/2
黄金比や白銀比を更に一般化した概念として、貴金属比というものがあります。第1貴金属比(n=1)が黄金比、第2貴金属比(n=2)が白銀比です。ちなみに第3貴金属比(n=3)…1:{3+√13}/2 には青銅比という別名がついています。
(この並び、昔のアニメを思い出します)

おまけ

貴金属比に属さないながら、貴金属の名前を持つ比として白金比というものがあります。
白金比…1:√3
これは、正三角形の底辺の中点と頂点を結んでできる直角三角形の、斜辺を除く辺の比として出てきます。斜辺は短い方の辺のちょうど2倍になっていて、これは三平方の定理からも分かります。

最後に

少し前の話になりますが、京都大学数理解析研究所の望月新一教授による「ABC予想」の証明が認められたと話題になりました。「ABC予想」を真とすると、「フェルマーの最終定理(ワイルズの定理)」をあっという間に証明できるという話があるので紹介します。詳しい説明をしているサイトが他にありますので、ここでは定理と証明の概略を紹介するに留めます。
【フェルマーの最終定理(ワイルズの定理)】
3以上の自然数nについて、(x^n)+(y^n)=(z^n) を満たす自然数の組(x, y, z)は存在しない。
【証明の概略】
・ABC予想を真とすると、n>=6で(x^n)+(y^n)=(z^n) を満たす自然数の組(x, y, z)は存在しないことが証明できる。
・n=3,4,5の場合は、先人達によって個別に証明されている。
・よって3以上の自然数について、(x^n)+(y^n)=(z^n) を満たす自然数の組(x, y, z)は存在しない。
証明に360年かかった命題を、ここまで簡単に証明してしまえるのは驚くばかりです。しかしながら全てではなく、先人達が残した実績との合体技で証明です(これも昔の某アニメを思い出す)。どんなに優れた知見が出ても、そこに至るまでの先人達の歩みを無視することはできないと感じます。
今回は、ここで終わりとします。

ImageMagickコマンドの注意点

こんにちは。イメージマジック三浦です。 2019年が後半に入りました。あっという間に半年過ぎたとも、まだ半年あるんだなとも思います。 今回は「ImageMagickコマンドの注意点」と題して書きます。
業務で、WindowsOSに導入することを念頭に ImageMagickコマンドを調査しましたが、その時に気づいたことを書いてみます。

ImageMagickとは

画像の変換や表示、内部情報などを見るソフトウェア群です。詳しい説明は他のサイトを参照してください。
 https://imagemagick.org/index.php 

インストールについて

ImageMagick開発元が、様々なOS向けにインストーラを提供しています。
特にWindowsOS用では、インストーラ方式のものと解凍して配置するだけのものがありますが、今回はWindowOS向けのポータブル版を使うことにしました。 ImageMagickコマンドの動作には、VC2013再頒布可能パッケージのインストールが必要とされています。最初のパッケージの後に、更新版が出ていますので、導入が必要だとすれば更新版の方がよいかと思います。
(VC2013再頒布可能パッケージ)
https://www.microsoft.com/ja-jp/download/details.aspx?id=40784
(VC2013再頒布可能パッケージの更新版)
https://support.microsoft.com/ja-jp/help/3138367/update-for-visual-c-2013-and-visual-c-redistributable-package
ただし、OSによっては同パッケージをインストールしなくても動いてしまうケースがあるので、実際の画像を使った動作確認は欠かせません。

動作確認をしてみて気づいたこと

WindowsOS向けのポータブル版ImageMagickを動作確認をする中で、ImageMagickコマンドの1つ「convert」が動かないという現象がありました。 以下のようなディレクトリ構成で、
E:\hoge
[D] ImageMagcik
[F] test.psd
PowerShellで、カレントディレクトリをImageMagick直下に移動してconvertコマンドを実行すると、以下のような結果が出ます。
(convertコマンドの詳細は割愛します)。
PS E:\test\ImageMagick&amp;gt; convert ../test.psd[0] ../test.png
無効なパラメーターです - /test.psd
PS E:\test\ImageMagick&amp;gt;
1つ上の階層のディレクトリに「test.png」というファイルができると期待したのですが、エラーになりました。
「convert /?」と入力するとWindowsOSのコマンドのヘルプが出てきたので、ImageMagickの「convert」コマンドを実行するつもりが、WindowsOSの「convert」コマンドを実行していたと考えられます。特に、コマンドプロンプトだと発生しないので、混乱しやすいところです。 PowerShellで期待した結果を得るには、以下のように指定する必要がありました。
PS E:\test\ImageMagick&amp;gt; ./convert ../test.psd[0] ../test.png
または
PS E:\test\ImageMagick&amp;gt; .\convert ../test.psd[0] ../test.png

同じような現象を回避するために

今回のような現象を回避するには、先頭に「.\」を付加してカレントディレクトリのコマンドであると明示する方法が無難だと感じました。「.\」が付いていれば、コマンドプロンプトでもPowerShellでも同じ動きをしてくれるためです。 他には「convert」コマンドが、最新バージョンの1つ前のImageMagick6系と互換性を維持するために提供されるものなので、最新バージョンの「magick」コマンドを使うことが考えられます。

最後に

これから暑くなるので、体調の管理には気を付けてください。
個人的には、睡眠時間の確保と毎日の食事でネバネバ食材を食べることに気を付けていますが、身体が軽くなったように感じているので、これからも続けていきたいと思います。

PowerShellにLinuxコマンドを見る

こんにちは。イメージマジック三浦です。
朝晩の寒さは残りますが、梅の花が咲いているところやプロ野球オープン戦の話題が出てくるようになりました。一方で最近降った雨は冷たかったです。暖かくなるのはもう少し先のようです。

今回のテーマ

今回は「PowerShellでLinuxコマンドを使ってみる」と題して書きます。 かねてより、lsやcurlを起動できることは知っていたのですが、 気になったので正体を調べてみました。

早速試す

実際にPowerShellでコマンドを打ってみると、以下の結果となります。オプションまでLinuxと同じということはないようです。
  • 使える:ls, ls -R
  • 使えない:ls -l
PS C:\hogehoge> ls


    ディレクトリ: C:\hogehoge


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2019/03/01     12:42                subdir
-a----       2019/03/01     12:41              3 aaa.txt
-a----       2019/03/01     12:41              3 bbb.txt
-a----       2019/03/01     12:41              3 ccc.txt


PS C:\hogehoge>
PS C:\hogehoge> ls -R


    ディレクトリ: C:\hogehoge


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2019/03/01     12:42                subdir
-a----       2019/03/01     12:41              3 aaa.txt
-a----       2019/03/01     12:41              3 bbb.txt
-a----       2019/03/01     12:41              3 ccc.txt


    ディレクトリ: C:\hogehoge\subdir


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019/03/01     12:42              6 ddd.txt


PS C:hogehoge>
PS C:\hogehoge> ls -l
Get-ChildItem : パラメーター 'LiteralPath' の引数が指定されていません。型 'System.String[]' のパラメーターを指定し、再
試行してください。
発生場所 行:1 文字:4
+ ls -l
+    ~~
    + CategoryInfo          : InvalidArgument: (:) [Get-ChildItem]、ParameterBindingException
    + FullyQualifiedErrorId : MissingArgument,Microsoft.PowerShell.Commands.GetChildItemCommand

PS C:\hogehoge>

昔からあるWindowsのコマンドは?

ディレクトリ配下の一覧を返すコマンドとして、以前からdirというものがありました。ここでdirを実行してみると、lsと同じ結果が返ってきます。
PS C:\hogehoge> dir


    ディレクトリ: C:\hogehoge


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2019/03/01     12:42                subdir
-a----       2019/03/01     12:41              3 aaa.txt
-a----       2019/03/01     12:41              3 bbb.txt
-a----       2019/03/01     12:41              3 ccc.txt


PS C:\hogehoge>

正体を探る

lsとdirで同じ結果が出るのは判りましたが、何故かはわかりません。これを調べるために、ls のヘルプを見てみます。
PS C:\hogehoge> ls -?

名前
    Get-ChildItem

概要
    Gets the items and child items in one or more specified locations.

…
Get-ChildItem という名前が出てきました。これが正体のようです。
lsを実行すると、「Get-ChildItem」というPowerShellコマンド(コマンドレットと言われます)が実行されます。 dir でも同じ結果が出てきます。
PS C:\hogehoge> dir -?

名前
    Get-ChildItem

概要
    Gets the items and child items in one or more specified locations.

…

「Get-Children」の別名を確認

最後に、「Get-Children」コマンドレットが持つエイリアスを確認してみます。
PS C:\hogehoge> Get-Alias | Where-Object { $_.Definition -eq "Get-ChildItem" }

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Alias           dir -> Get-ChildItem
Alias           gci -> Get-ChildItem
Alias           ls -> Get-ChildItem


PS C:\hogehoge>
ls, dir の他に gci という別名がいることも分かります。

まとめ

PowerShellで「ls」や「curl」が打てた、というところを出発点に調べてみました。Linux系OSで使えたコマンドを試しに打ってみたら実は使える、というものが他にもあるかもしれません。また使えなかったとしても、自分でエイリアスを設定することもできるようです。
スクリプトを作る時は、使おうとしているエイリアスが実行先の環境で設定されているかを確認する必要がありますが、技術的な選択肢の1つの手段として、上手く使っていければと思います。

大量の情報から必要なものを探す(メール編)

こんにちは。イメージマジック三浦です。
昨年、月に1~2回やっているフットサル用に、長袖のトレーニングウェアを買いました。フットサルは屋外でやることが多いのですが、肌が直接外気に触れないだけで体の冷え具合が全く違うので、寒い時期の運動にはありがたい存在です。

今回はメールの中から必要な情報を取り出す、ということをテーマにして投稿します。

【システムからの通知メール】

弊社で運用しているシステムからは、日々多くのメールが送信されてきます。
多くは定時のバッチやタスクが「異常なし」で動作したことを通知してくるメールですが、 時々異常を通知するメールが送信されることがあります。
異常を感知した場合は、内容を確認して必要な対応を取らなくてはいけません。

通知メールは1通ずつ見ていくのが大変なくらいに送信されてきますが、 異常がない時に「異常なし」と通知してもらうことも重要です。「問題なし」の通知を止めてしまうと、異常がなくて通知がないのか何らかの異常で通知できないのか判断しにくくなります。

そこで、送信されてきたメールの中からエラーを通知するメールを取り出すことを考えます。

【キーワードの選定】

ここではシステムからの通知メール本文に
「error: x」(x = 1から9の整数)
という文字列があれば、何かしらの異常があるものと判断します。
異常が1件でも10件でも、「error: 1」という文字列で検索できるということです。

【どうやって抽出するか】

正規表現による検索が使えるメーラーの場合、 上記の規則性をカバーできる正規表現で条件を設定すれば簡単です。 しかし、検索に正規表現を使えないメーラーの場合は悩みます。
※普段の業務ではgmailを利用していますが、gmailは正規表現が使えません。

【実際にやったこと】

gmailでは、必要な分だけOR条件をつなげる方法が使えました。
“error: 1” OR “error: 2” OR … (9までつなげる)
検索条件のテキストを別ファイルに保存しておいて、gmailフィルタのラベル添付機能と組み合わせると、使いやすい状態になりました。

【まとめ】

今回は、大量の情報から必要な情報を探すという観点で書きました。
目の前にある情報をどう整理するかという観点と、どういう情報があれば整理しやすいかという観点の両方を忘れないようにしたいと思います。

コマンドの合わせ技でsymfonyを起動する

こんにちは。10月に入社しました三浦です。

10月22日に弊社の引っ越しがありました。
いくつもの机や椅子を組み立てて、新オフィスでの業務を迎えています。
組み立て中は、別の方が持参した電動ドリルが大活躍しました。
バッテリー切れでヘトヘトになっていたので、持ち主の方から電気をもらっていることを祈ります。

さて本題ですが、今回はsymfonyの起動コマンドをコマンドラインで生成させてみます。
symfonyを自習していた当初、手動でifconfigを打ってIPアドレスを確認してからコマンドに入力していましたが、
何回も打っていたのでIPアドレスを自動で取得できる方法を考えてみました。

今回のテーマ

linux搭載のコマンドだけで、symfony起動コマンドを生成する。

前提条件

以下条件の下で、ホストOSのWebブラウザからsymfonyサイトにアクセスすることを考えます。
1. ホストOS:Windows10、VM:ubuntu18.04
2. 両方ともIPアドレスはDHCPによる自動付与とする。
3. VM上でsymfonyを稼働させる。

ポイント

3の前提より、単にserver:startを実行するだけではアクセスできません。

php bin/console server:start

代わりに、アクセスするIPアドレスとポート番号を明示して起動します。

php bin/console server:start {VMのIPアドレス}:{port番号}

IPアドレスの取得

ifconfigの出力結果から、IPアドレス(XXX.XXX.XXX.XXXの部分)を抜き出してみます。
ここでは、ホストOSと通信できるVM側のNICをeth0とします。

$ ifconfig eth0
eth0: flags=4163&lt;UP,BROADCAST,RUNNING,MULTICAST&gt;  mtu 1500
        inet XXX.XXX.XXX.XXX  netmask YYYY.YYY.YYY.YYY  broadcast ZZZ.ZZZ.ZZZ.ZZZ
        inet6 aaaa::bbbb:cccc:dddd:eeee  prefixlen 64  scopeid 0x0&lt;global&gt;
        ether ff:gg:hh:ii:jj:kk  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ifconfigの結果から抜き出すコマンドは以下の通りです。

echo `ifconfig eth0 | grep inet | grep -v inet6 | awk -F' ' '{ print $2 }'` … (a)

symfony起動コマンドの生成

(a)のコマンドを実行して出力されるIPアドレスをシェル変数に格納し、symfonyの起動コマンドを作成します。
なお、ポート番号は8000番を指定しています。

cd {起動するプロジェクト名}
SERVER_IP=`ifconfig eth0 | grep inet | grep -v inet6 | awk -F' ' '{ print $2 }'`
php bin/console server:start ${SERVER_IP}:8000

もう一工夫する

symfony起動中の時に server:start を実行するとエラーになってしまいますので、symfonyが停止中の時だけコマンドを発行するようにします。
今回は、プロジェクトフォルダ直下に「.web-server-pid」がなければ、symfony停止中と判断します。
if文を使って、symfony停止中の判断を追加します。

cd {起動するプロジェクト名}
if [ ! -f .web-server-pid ]; then
  echo `ifconfig eth0 | grep inet | grep -v inet6 | awk -F' ' '{ print $2 }'`
  php bin/console server:start ${SERVER_IP}:8000
fi

これで、IPアドレスを都度取得してsymfonyを起動するコマンドが出来上がります。
別ファイルに保存して適切なパーミッションを設定すれば、ワンタッチでsymfonyを起動するシェルとになります。

最後に

小さいコマンドながら、自分で作れたときは達成感を感じました。
これからも学ぶことは多いですが、今回のような達成感を糧に進んでいきます。