信頼はずっと、挑戦はもっと。

お問い合わせ
TEL:03-3496-3888

BLOG コアテックの社員ブログ (毎週月曜~金曜更新中)

LIST OF ARTICLES

記事一覧

  • テクログ

    素敵だら! Linux コマンド ~番外編-ターミナルショートカットキー~

    こんにちはうなぎです。IT開発者にとって避けては通れない数ある Linux コマンドのうちから、厳選してご紹介する不定期コーナー“素敵だら! Linux コマンド”。今回は番外編で、ターミナル(inux の cli)上でコマンドを実行する上で便利なショートカットキーのご紹介です!■カーソルを先頭に移動する `Ctrl + a`しばしばあるシーンとしては、mkdir /path/to/dir などのように特定のディレクトリを作成した後で、そのディレクトリを操作したい、移動したいなどの時、`mkdir` のコマンドだけ他のコマンドに変えられると、わざわざディレクトリを書き直す必要がないので楽ですが、そういう時には `Ctrl + a` を実行して、カーソルを先頭に移動させましょう!※`$_` とすると、直近で使用したパラメータにアクセスできるので、 `mkdir` 直後であれば、`cd $_` などとした方がより簡単だったりします※`$_` のような cli 上の変数をもっと知りたい場合は下記参照してください https://stackoverflow.com/questions/5163144/what-are-the-special-dollar-sign-shell-variables https://www.gnu.org/software/bash/manual/html_node/Variable-Index.html■カーソルを末尾に移動する `Ctrl + e`こちらも同じように、コマンドの最後の方のパラメタだけ変えたいなどの場合に、使用することがあるかもしれません。※私は多分あまり使ってないです■カーソルから先頭まですべて削除する `Ctrl + u`上下カーソルキーで、コマンド履歴の中にいるようなとき表示されたコマンドを一発で消したくなったらこれです。※`Ctrl + c` でも多分消えますが、もしかしたら環境によっては違うかもです■カーソルから末尾まですべて削除する `Ctrl + e`こちらはカーソルがコマンドの途中を書き換えて、そこから後ろはいらない!といった時などに使います。■過去に実行したコマンドを検索する `Ctrl + r`過去に実行したコマンドにさかのぼって実行したいときには、直近であれば上カーソルキーで、一覧で見たいときには `history` と `grep` の組み合わせで、特定の一つを検索して実行したいときにはこの `Ctrl + r` が便利です。`Ctrl + r` を実行して、検索モードに入り「この間実行したコマンド、たしか、こんな感じだったよなー」って考えながら、打ち込むとコマンドの候補が表示されます。検索モードは `Ctril + c` で抜けられます。以上、ターミナルの便利なショートカットキーでした!他にもあると思うので、よりよいCLI生活をするために是非確認してみてください!

  • テクログ

    bfcache と popstateイベント ~地獄のブラウザバック友達~

    どうも!最近、「地獄のブラウザバック友達」ができたわいです!親にはあの子たちとは付き合うなと言われていたのですが、、はい。ブラウザバックを検知して発火するpopstateイベントまず、今回実装しようとした内容は、一覧ページがあって、そこで詳細ボタンをクリックすると、詳細ページがモーダルとして表示されるそして、そのモーダルには一覧ページに戻るための閉じるボタンがあり、ブラウザバックボタンを押下した場合も遷移せず、閉じるボタンと同じ挙動をするというものです。上記の挙動は、History API を使用して実装しました。(簡潔に書くためにjQueryを使ってます。すみません)// 詳細ボタンを押下 $(document).on('click', '.js-detail-open', function() {     history.pushState(null, null, '{$詳細ページのurl}'); // ブラウザバック検知用に履歴を追加     modalOpen(); // モーダルを開く処理 }); // モーダル閉じるボタンを押下 $(document).on('click', '.js-detail-close', function() {     // popstateイベントを発火させる(ブラウザバック押下時と同じ挙動に)     history.back(); }); // ブラウザバック押下時にページ遷移せず、動的に追加したStateが履歴から取り除かれたときに発火 window.addEventListener('popstate', function() {     modalClose(); // モーダルを閉じる処理 }); 簡単に書きましたが、モーダル閉じるボタン押下時に「history.back();」させて履歴を取り除いておくとか、下記bfcacheに出くわしてからいろいろ改善しました。ポイントは、JSで動的に追加した履歴でないと、ブラウザバック時にpopstateイベントが発火しないというところです。挙動についてのイメージは、下記記事が参考になるかと思います。イメージさえ掴めれば、そこまでややこしい話ではないと思います。ブラウザバックをトリガーにしてイベントを発火させる方法JSも含めてすべてのページ情報をメモリにキャッシュするbfcachebfcacheはWebページを速く表示させるためのブラウザの機能です。詳細については、下記記事が参考になりました。Back/forward cache簡単に言うと見出しの通りで、具体的にどのようなことが起こるかというと、ハンバーガーメニューを開いてリンクをクリックして、遷移した先でブラウザバックをすると、ハンバーガーメニューが開いたままで先ほどのページが表示されるとかです。以前ロードしたJSがそのまま使われて、ブラウザバック時にJSをロードしてくれない。ページを離れたときの状態がそのまま再現されるというものです。今回、このbfcacheが iOS の Safari / Chrome の両方で発生しました。ブラウザバック時にキャッシュされているpopstateイベントが発火してバグるbfcacheのせいで、ハンバーガーメニューが開いているだけならまだよかったのですが、初回アクセス時に設定したpopstateイベントがブラウザバック時にそのまま発火して、モーダルを閉じる処理(省略してますが、結構複雑…)を実行してしまうバグにハマりました。bfcacheをJSで検知するには、PageTransitionEvent.persisted を使えば良いそうです。ということで、下記記事を参考にして、ページ読み込みがbfcacheからであれば、JSで強制リロードしてもう一度JSをロードするという処理を実装しました。ブラウザの「戻る」ボタンでキャッシュが表示されてしまう際の対策これで一件落着かと思えば、、、悲劇が起きました、、連続で該当処理が入っているページへ遷移していき、その後ブラウザバックを連続で押していくと、ブラウザバック2回まではページのリロードという期待通りの動きをしてくたのですが、3回目のブラウザバック時に、JSでbfcacheを検知できない(bfcache自体が発動していない?)にもかかわらず、popstateイベントが発火してしまいました。ブラウザの挙動を見る感じ、履歴をpushStateで余分に追加した分のpopstateイベントが発火していました。つまり、適切に履歴を取り除いていかないとこの問題は解決できないということです。そこらへんはブラウザがよきにはからってくれると思っていましたが、そんなに甘くありませんでした。最終的には、下記のような実装で、無事「地獄のブラウザバック友達」と仲良くなることができました。let isBFCache = false; window.onpageshow = function(event) {     // bfcacheを検知、popstateイベントより先に発火     isBFCache = event.persisted; }; // 詳細ボタンを押下 $(document).on('click', '.js-detail-open', function() {     history.pushState(null, null, '{$詳細ページのurl}'); // ブラウザバック検知用に履歴を追加     modalOpen(); // モーダルを開く処理 }); // モーダル閉じるボタンを押下 $(document).on('click', '.js-detail-close', function() {     // popstateイベントを発火させる(ブラウザバック押下時と同じ挙動に)     history.back(); }); // ブラウザバック押下時にページ遷移せず、動的に追加したStateが履歴から取り除かれたときに発火 window.addEventListener('popstate', function() {     if (isBFCache) {         isBFCache = false;         return false;     }     modalClose(); // モーダルを閉じる処理 }); いざ該当箇所のコードだけ抜き出してみると考えやすいですが、ソースコードの方は複雑すぎて、なかなか思考の整理ができませんでした。思考が整理できさえすれば、あとはやることが決まってくるので、いかに論理的に問題を分割できるかということが重要なスキルだなと改めて感じました。まあ、そんな大層なことは言わなくていいんですよ。友達が増えたので、それでOKです。以上、わいでした。健闘を祈る!!

  • 画像:ブログサムネイル

    テクログ

    Go言語でマークダウンファイルをhtmlファイルに変換する処理を作る

    はじめまして。たなしょです。   日頃から技術的なことをメモをする際にマークダウン形式でメモを取っているのですが、それをブログに上げる際にいつもvscodeでhtmlファイルにコンバートをかけていました。そこでGo言語の学習のついでに変換機を自作しようと思い作ることにしました。まだまだ追加で実装しなくてはいけないところが多々ありますがある程度メイン部分が完成したので機能や苦労した点、今後の改良点をここに記載したいと思います。長くなってしまったので要約です。TL;DR・ごく一部の文字は変換することができた。・苦労した点はcodeタグ内の「<>」の扱いと、pタグ内に文字をどのように入れるか。・今後はaタグや画像の出力、リストなどにも対応していきたい。・息抜きに違う言語で開発する際はぜひGo言語を。実行結果変換したhtmlを画面で見るとある程度読めなくはないのかなという感じでしょうか。ファイルの全体や各ファイルについて全体的なファイルは下記のようになりました。. |-- Makefile |-- css.go |-- execute.go |-- generate.go |-- go.mod |-- main.go |-- paragraph.go |-- reg.go `-- test.md mdファイルとMakefileを除いて331ステップをGo言語で作成していました。cloc mvtohtml/     9 text files.     9 unique files.     1 file ignored. github.com/AlDanial/cloc v 1.88 T=0.02 s (326.4 files/s, 18156.6 lines/s) ------------------------------------------------------------------------------- Language           files     blank    comment      code ------------------------------------------------------------------------------- Go                6       62       10      331 Markdown             1       5       0       20 make               1       4       0       13 ------------------------------------------------------------------------------- SUM:               8       71       10      364 ------------------------------------------------------------------------------- 各ファイルの機能は下記のようになります。main.go 引数チェックやmdファイルのファイル名を取得する。  execute.go  htmlファイルに書き込む処理。   css.go css部分を書き込む処理。  generate.go 変換処理の大本。  paragraph.go  pタグの変換処理。  reg.go htmlファイルに変換する際の「<」と「>」が存在するか判定する処理。  機能詳細、苦労した点h1タグやpタグの判定は各行の1文字目~4文字目を見て判定しています。  brタグは各行の後ろ1〜3行目が空白かどうかで判定しcase文で各パターンを判別しています。func pattern_check(line string, codeline int, slice_arr *[]string) (int, string) { br_flg := 0 pattern := "NONE" html_line := "" h_string := "" // first string search if line == "" && codeline == 0 { html_line += "\n" return codeline, html_line } else if line == "" && codeline == 1 { html_line += "\n" return codeline, html_line } else { slice := strings.Split(line, "") length := len(slice) if slice[0] == "#" && slice[1] == " " { pattern = "H1" for i := 2; i < length; i++ { h_string += slice[i] } } else if slice[0] == "#" && slice[1] == "#" && slice[2] == " " { pattern = "H2" for i := 3; i < length; i++ { h_string += slice[i] } } else if slice[0] == "#" && slice[1] == "#" && slice[2] == "#" && slice[3] == " " { pattern = "H3" for i := 4; i < length; i++ { h_string += slice[i] } } // code check if line == "```" { pattern = "CODE" } // br check if slice[length-1] == " " && slice[length-2] == " " && slice[length-3] == " " { br_flg = 1 } // in code tag check if pattern != "CODE" && codeline == 1 { pattern = "INCODE" } if pattern != "NONE" { paragraph(pattern, &html_line, slice_arr) } switch pattern { case "H1": html_line += "<h1>" html_line += h_string html_line += "</h1>" case "H2": html_line += "<h2>" html_line += h_string html_line += "</h2>" case "H3": html_line += "<h3>" html_line += h_string html_line += "</h3>" case "NONE": if br_flg == 1 { for i := 0; i < length-3; i++ { html_line += slice[i] } html_line += "<br>" } else { html_line += line } html_line += "\n" paragraph(pattern, &html_line, slice_arr) case "CODE": if codeline == 0 { codeline = 1 html_line += "<pre>" html_line += "<code>" } else { codeline = 0 html_line += "</code>" html_line += "</pre>" } case "INCODE": rep_line := "" if reg(line) { rep_line = strings.Replace(line, "<", "&lt;", -1) rep_line = strings.Replace(rep_line, ">", "&gt;", -1) html_line += rep_line } else { html_line += line } } if pattern != "NONE" { html_line += "\n" } return codeline, html_line } } 苦労した点は2点あり、一点目はcodeタグ内のh1タグなどの「<>」がhmtlがファイルに変換されるとそのままタグとして読み込まれてしまうため文字列に置き換えるところでした。解決策は「INCODE」パターンに入ったときにreg.goを呼び出して「<>」が存在するかを判定して、存在した場合は「<」は「&lt;」、「>」は「&gt;」に変換をかけることにしました。  case "INCODE"詳細   case "INCODE": rep_line := "" if reg(line) { rep_line = strings.Replace(line, "<", "&lt;", -1) rep_line = strings.Replace(rep_line, ">", "&gt;", -1) html_line += rep_line } else { html_line += line } reg.go詳細   goの標準ライブラリであるregexpと正規表現を利用して各タグが行内に存在するか判定しています。package main import ( "regexp" ) const ( HEADING  = `</?h[1-6]>` PARAGRAPH = `</?p>` CODE   = `</?code>` BREAK   = `<br\s?/?>` ) func check_regexp(reg string, str string) bool { flg := regexp.MustCompile(reg).Match([]byte(str)) return flg } func reg(text string) bool { reg_flg := false regexp_arr := [...]string{HEADING, PARAGRAPH, CODE, BREAK} for _, v := range regexp_arr { reg_flg = check_regexp(v, text) if reg_flg { break } } return reg_flg } 2点目は「#」や「```」(ここでは特殊文字という)以外の文字が1文字目に現れた場合はpタグ内に格納し、特殊文字が現れた場合はpタグを閉じるようにするところです。   解決策はパターン「NONE」で文字列スライスが空の場合はpタグと行を文字列スライスポインタへ格納、文字列スライスに文字が入っている場合は行を文字列スライスポインタへ格納、パターンが「NONE」以外ならpタグを文字列スライスに格納して文字列スライスポインタ内の文字列をhtmlファイルに出力する方法で解決できました。  以下paragraph.go の詳細です。  package main func is_Empty(s *[]string) bool { return len(*s) == 0 } func slice_Delete(s *[]string) { res := []string{} *s = res } func slice_Join(s *[]string) string { var str string for _, v := range *s { str += v } return str } func paragraph(pattern string, html_line *string, slice_arr *[]string) { if pattern != "NONE" { if !(is_Empty(slice_arr)) { *slice_arr = append(*slice_arr, "</p>\n") *html_line = slice_Join(slice_arr) slice_Delete(slice_arr) } } else { if is_Empty(slice_arr) { *slice_arr = append(*slice_arr, "<p>") *slice_arr = append(*slice_arr, *html_line) } else { *slice_arr = append(*slice_arr, *html_line) } *html_line = "" } } 今後の改良点まだまだpタグ、codeタグ、hタグにしか対応してないので今後はaタグや画像の出力、リストなどにも対応していきたいです!おわりに皆さんは業務でPHPを使うことが多いと思いますがたまには息抜きに違う言語で開発してみるのはどうでしょうか?   特にインタプリタ型の言語とコンパイル型の言語では考え方が違うためとてもいい勉強になると思います。  その際はぜひGo言語は勉強してみてはいかがでしょうか?   今回のソースの詳細は下記リンク にあるのでマークダウン変換機能を作る際は参考にしてみてください(笑)https://github.com/jacoloves/lab/tree/master/go_tool/mvtohtml  

  • テクログ

    超便利なツール「Magnet」って知っていますか?

    初めまして!11月からコアテックに入社しました、りょうたです。今回はMacの使い勝手をよくする Magnet というツールを紹介させてください!パソコンで作業している時、複数の画面を同時に開いて作業したい時ってありませんか?僕はエディタとブラウザを同時に開いて作業することが多いです。そういう時、みなさんはどうしていますか?えっ、手動で画面表示サイズを調整しているんですか!!それ、めっちゃくちゃ面倒じゃないですか、、僕も最初はそうしてたんですよ。でも頻繁に画面を切り替えたり見比べるので、それはもう骨が折れすぎて粉々になるくらい面倒な作業でした。一応Macには標準で画面分割する機能があるのですが、これが使いづらい、、そんな悩みを解決するのが、このMagnetというツールなんです!!ショートカットキーで画面を簡単に分割できるんですよ。例えば、controll・option・←または→ で左右分割、controll・option・↑または↓ で上下分割、controll・option・U, I, J, K でそれぞ左上・右上・左下・右下に4分の1分割といった具合にやることで、左右に分割:左上・右上・下に分割:こんな感じに一瞬で分割してくれます。一つの画面だけを全体表示にしたければ、controll・option・Enter 同時押しです。難点は、、、有料(500円)です(泣)でも僕は10秒使って思いましたね、「ああ、元とったわ」って。作業効率が上がること間違いなしなので、是非試してみてください!探すのが面倒な方はこちらからどうぞhttps://apps.apple.com/jp/app/magnet-%E3%83%9E%E3%82%B0%E3%83%8D%E3%83%83%E3%83%88/id441258766?mt=12

  • テクログ

    ターミナルって意外と便利だよって話

    挨拶初めまして!昨年にコアテックへ参加しましたとみーです! どうぞよろしくお願いします!!お正月は某惑星フロンティアでチャンピオンになるべく戦ってたり、某宇宙世紀ロボに乗り、傭兵となって戦う○○オペレーション2をやってたりしてました!それで終わりにするには色々とアレだろと思いましたので、一つ技術っぽいことでも書かせてもらおうと思います。ターミナルコマンドを話す前の前置き(読み飛ばし可)みなさん、ターミナルって知ってますか!?macに初期から設置されているCLIツールです!!より詳しい説明とかは「ターミナル mac」 とかでググれば沢山でてきますので省略します!windowsではコマンドプロンプトってやつが同じようなツールになりますが叩く事が出来るコマンドは色々と違います。 今回はターミナルコマンドを書かせて貰います。突然ですが、こんな出来事に出会ったことありませんか!?例えば、とあるWebで野菜を販売している八百屋さんのお話です。「りんご」や「キャベツ」など、様々な野菜や果物を売っておりましたが、今度新しく「青森県産のりんご」や「群馬県産のキャベツ」など、特別な産地を追加するとのこと!お客さんに見せる画像データも、特別美味しそうに見える別の物にしたいと言う話です!画像の新規追加や差し替えはとみーの仕事です!画像を受け取ったら早速やりましょう!お、やってきたやってきた!これが新しい画像たちが入ってるディレクトリ(フォルダ)ですね!!!カチカチッ(ダブルクリック)00001.jpg00002.jpg00003.jpg以下10000件以上の画像…うわあああぁぁぁぁぁぁ!?!!?「青森県産のりんご」や「群馬県産のキャベツ」の画像がどれか、わからないのである早速、八百屋さんにお問い合わせ。八百屋さんもすぐに対応してくれて、対応表を送ってくれました! やったね!!00001.jpg 青森県産のりんご00002.jpg 群馬県産のキャベツ00003.jpg 普通のりんご以下10000件以上の画像…なるほど、これでどれが何の画像なのか、わかるようになりました!あとは、このまま表示させちゃえばおしまいです!!「青森県産のりんご」は「00001.jpg」の画像を表示させてー。「群馬県産のキャベツ」は「00002.jpg」の画像を表示させてー……よしっ!(某指差し猫感先輩に怒られました。。。番号だけじゃわからない。 突然、まったく知らない「千葉県産のほうれん草」画像を差し替えて欲しいって言われたらどうするの?とのことおっしゃるとおり! それじゃ、この画像たちの名前を変えましょうか! うん!!……うん? この1万件以上の画像を……?ターミナルコマンドさぁ前置きが長くなりましたが、ここからが本番です。この1万件以上の画像名、それっぽいものに変えましょうという作業です。手元にあるのは以下のデータです。1万件以上の画像データ「〇〇の〇〇」という文字列 さて、それではターミナルコマンドの時間です。※注意ターミナルコマンドで今回書く内容「ファイル名変更」は基本的には不可逆です。元に戻すのは難しいです。失敗しても良いように必ずバックアップを取ったり、みんながアクセスする環境で何かを作ったり、移動させるコマンドを叩くのは出来る限りやめましょう。1.「青森県産のりんご」のような文字列だけを抜き取ります$ grep '.*の.*' 画像データリストのファイル > ○○の〇〇.txt 2.抜き取った「〇〇(都道府県や[普通])の」と「の〇〇(野菜や果物)」の全ての種類を洗い出す$ cat ○○の〇〇.txt | sed -e 's/の.*//g' |  sort -u > 都道府県や普通.txt $ cat ○○の〇〇.txt | sed -e 's/.*の//g' |  sort -u > 野菜や果物.txt 3.抜き取ったデータを元に、変換後の文字列名をつくりますここは手作業です。 「青森県産」→「aomori_」「りんご」→「apple」などに変換する対応表を作ります4.都道府県などの対応表を元にして、コマンドを叩き続けます(都道府県の関係上、どんなに最低でも47回を超えます)$ sed -e 's/青森県産の/aomori_/g' 画像データリストのファイル > 画像データリストのファイル_new $ sed -e 's/群馬県産の/gunma_/g' 画像データリストのファイル_new > 画像データリストのファイル_new … 5.野菜も同様に、[.jpg]という文字列を追加してコマンドを叩き続けます(全種類分、叩きます)$ sed -e 's/りんご/apple.jpg/g' 画像データリストのファイル_new > 画像データリストのファイル_new … 6.「画像データリストのファイル_new」を使って、画像データの名前を変換$ cat 画像データリストのファイル_new | xargs -I % mv % 以上!これで「00001.jpg」という「青森県産のりんご」の画像名を「aomori_apple.jpg」に変換する、という作業を1万件以上の画像にそれぞれ対応出来たはずです!(遥か大昔の案件を参考に身元が割れないよう改変して書いてるので、 コマンドが汚いとかいう意見があったり細かいところでミスが出る 可能性があるかもしれません。 何かしらの形で聞いてくれたら答えたいです)いや、待って。 都道府県とかで最低47回以上コマンド叩くの、面倒くさくない?4、5番の対応表を元にする手作業の部分ですね!? わかります!!似たようなコマンドをバシバシ叩くことでミスが出ますし、ここは直すべきですね!しかし、とみーが知ってるターミナルのコマンドは、引数は一つまで!つまり、とみー流のターミナルのコマンドじゃ対応できません!!なので、今度はシェルの話をしましょうか(次の機会があればです)ここまで長文を読んで頂いた皆様方、ありがとうございます!それでは〜

  • テクログ

    Chatwrok API でダイレクトチャットに通知を送る方法

    要点Chatwrok API でダイレクトチャットに特殊な通知をしたい場合は、https://api.chatwork.com/v2/contacts でコンタクト一覧が手に入るので、そこにある該当アカウントの room_id にメッセージを送ります。背景アカウント作成を ChatOps でやりたいパスワードも自動発行して、発行コマンドを叩いたユーザーにだけ通知させたいたったこれだけです。これだけですが、正式名称であるダイレクトチャットで探しても出ない出ない。「Chatwork API ダイレクトチャット」などで Google 検索しても、「結局どうするの?」状態です。私の探し方が悪いのかもしれませんが、このままだと癪なので、自分で方法を書くことにしました。ダイレクトチャットへの送信方法1. ダイレクトチャット用の部屋番号を取得まずダイレクトチャット用の部屋番号を取得します。というよりも、ここができたら後は幾らでもネット上に情報があります。以下、 curl での実行例です。 curl -H "X-ChatWorkToken: APIトークン" "https://api.chatwork.com/v2/contacts" この結果、以下のような JSON がレスポンスとして返ってきます。[  {   "account_id": 000000,   "room_id": 000000000,   "name": "チャットワーク",   "chatwork_id": "chatwork",   "organization_id": 000000,   "organization_name": "チャットワーク",   "department": "",   "avatar_image_url": "https://appdata.chatwork.com/avatar/ico_avatar_notfound.png"  }, . . . ] ダイレクトチャットを送りたいアカウントのID と同じ account_id でフィルタリングを行い、room_id を取得します。jq で取得するならこうです。 curl -H "X-ChatWorkToken: APIトークン" "https://api.chatwork.com/v2/contacts" | jq '.[] | select(.account_id==ダイレクトチャットを送りたいアカウントのID)' | jq '.room_id' 2回パイプ噛ませる必要はない気もしますが、jq で複雑な条件のフィルタリングをしようとするとやり方を忘れるので、こうしています。2. ダイレクトチャットを送信する上で取得した部屋番号に送るだけです。それ以外は通常の使い方のままです。 まとめChatwrok API でダイレクトチャットに特殊な通知をしたい場合は、https://api.chatwork.com/v2/contacts でコンタクト一覧が手に入るので、そこにある該当アカウントの room_id にメッセージを送る!