tag:crieit.net,2005:https://crieit.net/tags/Bash/feed 「Bash」の記事 - Crieit Crieitでタグ「Bash」に投稿された最近の記事 2022-12-29T16:47:37+09:00 https://crieit.net/tags/Bash/feed tag:crieit.net,2005:PublicArticle/18357 2022-12-29T16:47:37+09:00 2022-12-29T16:47:37+09:00 https://crieit.net/posts/Docker-run-bash-ash Docker run の起動時に任意コードを実行後 bash や ash を終了しない <p>この記事は、 <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2022/docker">Docker Advent Calendar 2022</a> の 23日目の記事だ。<br /> 空いていたので埋めちゃうよ。</p> <p>この記事では、 bash や ash で任意のコードの実行後、ターミナルを終了せずに入力待ちにする方法について紹介する。<br /> 特に、 <code>docker run</code> の実行後に、その環境を維持したまま入力待ちにすることを考える。</p> <p>例えば、 Windows コマンドプロンプトや PowerShell であれば、 <code>CMD /K ***</code> オプションや、 <code>-NoExit -Command ***</code> オプションで実現できるような内容だ。</p> <p>本来なら、 docker build にてその任意コードの実行後の内容をイメージにするべきだが、 わざわざ build するまでもないとか、 build できない事情などもあるかもしれない。</p> <p>ということで、 bash の場合と、 alpine などで使われる BusyBox ash それぞれについて、 <code>docker run</code> 実行時に任意コード実行後、ターミナルの入力待ちにする方法を紹介する。</p> <h2 id="bash の場合"><a href="#bash+%E3%81%AE%E5%A0%B4%E5%90%88">bash の場合</a></h2> <p>bash の場合、 <code>--rcfile</code> オプションにて、起動時に実行するコマンドを指定できる。</p> <p>ただし、 <code>--rcfile</code> はファイルを指定する必要があるため、 替わりに<strong>プロセス置換</strong>で実行コードを与えてやる方法をとる。</p> <pre><code class="console">user@hostmachine:~$ docker run --rm -it debian:bullseye bash -c "bash --rcfile <(echo 'ls && export '\''FOO=B A R'\'' && MY_TIME=\$(date)')" bin dev home lib64 mnt proc run srv tmp var boot etc lib media opt root sbin sys usr root@container:/# echo $FOO : $MY_TIME B A R : Thu Dec 22 15:00:00 UTC 2022 root@container:/# </code></pre> <p>プロセス置換はコンテナ内で実行される必要があるため、一旦 <code>bash -c</code> にてコンテナ内で bash 実行させ、その中で改めて <code>--rcfile</code> オプションを指定した bash を起動する流れとなる。</p> <p>実際に実行したいコマンドは、 echo で文字列として書き出す。<br /> 上記例では、 <code>ls && export 'FOO=B A R' && MY_TIME=$(date)</code> と言う文字列を echo させている。</p> <p>引用符が二重三重になっていて、エスケープが非常に難しくなっているので注意。</p> <h2 id="BusyBox ash"><a href="#BusyBox+ash">BusyBox ash</a></h2> <p>alpine 3.15 以降に含まれる BusyBox の ash であれば、意外にもプロセス置換が使える。</p> <p>しかし、 <code>--rcfile</code> に相当するオプションは残念ながら無い。<br /> 替わりに、 ash には <code>ENV</code> という環境変数に記載されたファイルを ash 起動時に実行する機能がある。</p> <p>これを使おう。</p> <pre><code class="console">user@hostmachine:~$ docker run --rm -it alpine:3.15 ash -c "ash -c 'export ENV=\$1;ash' -s <(echo 'ls && export '\''FOO=B A R'\'' && MY_TIME=\$(date)')" bin etc lib mnt proc run srv tmp var dev home media opt root sbin sys usr / # echo $FOO : $MY_TIME B A R : Thu Dec 22 15:00:00 UTC 2022 / # </code></pre> <p><code>ash</code> の <code>-c</code> のコマンドに対して引数を与える <code>-s</code> オプションを使ってプロセス置換のファイルを与え、 それを <code>$1</code> 経由で <code>ENV</code> 環境変数にセット。 その状態で再度 ash を起動させれば、 bash と同様のことが行える。</p> <p>ENV 環境変数に直接プロセス置換のファイルを指定せず、わざわざ一旦引数を経由させているのは、 入力を受け付ける ash プロセスが動いている間、 プロセス置換のファイルにアクセス可能にする必要があるためだ。<br /> 例えば、 <code>export ENV=<(echo 'command');ash</code> と実行しても、 ash 実行の段階ではプロセス置換のファイルが閉じられているので、コマンドは実行されない。</p> <h2 id="もうちょっと複雑な例"><a href="#%E3%82%82%E3%81%86%E3%81%A1%E3%82%87%E3%81%A3%E3%81%A8%E8%A4%87%E9%9B%91%E3%81%AA%E4%BE%8B">もうちょっと複雑な例</a></h2> <p>起動時に apt パッケージマネージャーのリポジトリを書き換える方法(Ubuntu):</p> <pre><code>docker run --rm -it ubuntu:22.04 bash -c "bash --rcfile <(echo 'sed -i -E '\''s%^(deb(-src|)\s+)https?://(archive|security)\.ubuntu\.com/ubuntu/%\1http://srv2.ftp.ne.jp/Linux/packages/ubuntu/archive/%'\'' /etc/apt/sources.list && apt update && FooBar=`date -uIs`')" </code></pre> <p>起動時に apk パッケージマネージャーのリポジトリを書き換える方法(Alpine):</p> <pre><code>docker run --rm -it alpine:3.15 ash -c "ash -c 'export ENV=\$1;ash' -s <(echo 'sed -i -E '\''s%^https?://dl-cdn\.alpinelinux\.org/alpine/%https://ftp.udx.icscoe.jp/Linux/alpine/%'\'' /etc/apk/repositories && apk update && FooBar=`date -uIs`')" </code></pre> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/74094552/how-not-to-terminate-after-carried-out-commands-in-bash">https://stackoverflow.com/questions/74094552/how-not-to-terminate-after-carried-out-commands-in-bash</a></p> advanceboy tag:crieit.net,2005:PublicArticle/18205 2022-05-31T10:06:10+09:00 2022-05-31T10:07:00+09:00 https://crieit.net/posts/c5307b3d3852347f82e034cbe8f554bf パスを追加したのに反映されない <p>PATHを通しているつもりなのに、反映されないことがありました。</p> <h3 id="環境"><a href="#%E7%92%B0%E5%A2%83">環境</a></h3> <p>zsh</p> <h3 id="現象"><a href="#%E7%8F%BE%E8%B1%A1">現象</a></h3> <p>例えば、.~/binにあるファイルscript.shを、ファイル名だけで実行できるようにしたく、<br /> .zprofileに、下記を追加しました。</p> <pre><code>PATH="$PATH:~/bin" </code></pre> <p>しかし、script.shと打ってみても、下記エラーとなりました。</p> <pre><code>script.sh not found </code></pre> <h3 id="対策"><a href="#%E5%AF%BE%E7%AD%96">対策</a></h3> <p>チルダ"~"の代わりに、"$HOME"にしたところ、動きました。</p> <pre><code>PATH="$PATH:$HOME/bin" </code></pre> pizza3900 tag:crieit.net,2005:PublicArticle/18186 2022-05-10T22:39:48+09:00 2022-05-10T22:39:48+09:00 https://crieit.net/posts/docker-php-7-alpine-use-composer-and-xdebug-20220510 Docker の php:7-alpine イメージで Composer と Xdebug を使えるようにする <h2 id="経緯"><a href="#%E7%B5%8C%E7%B7%AF">経緯</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/slimphp/Slim-Skeleton">slimphp/Slim-Skeleton</a> を利用したいと考えました。</p> <p>手元の端末で開発しようとしたところ、 XAMPP に Xdebug を入れ忘れていたことに気付きました。しかも、 XAMPP の PHP のバージョンで Xdebug のインストールが止まってしまいました。</p> <p>そこで、スケルトンプロジェクトに付随している Docker Compose での開発を試みましたが、この Docker Compose 内で pull してくるイメージは Composer も Xdebug もないので入れることにしました。</p> <h2 id="コード"><a href="#%E3%82%B3%E3%83%BC%E3%83%89">コード</a></h2> <h3 id="ディレクトリ構造"><a href="#%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E9%80%A0">ディレクトリ構造</a></h3> <pre><code>root/ ├ _docker/ // Dockerに関する設定置き場 │ ├ dockerfiles/ │ │ ├ settings/ │ │ │ └ php.ini // PHPの設定 (Xdebug 有効化) │ │ │ │ │ └ Dockerfile │ │ │ ├ mysql/ // MariaDB 用ディレクトリ │ │ └ .gitkeep │ │ │ └ phpmyadmin/ │ ├ conf/ │ │ └ phpmyadmin-misc.ini // phpMyAdmin の設定 (メモリ上限など) │ │ │ └ sessions/ │ └ .gitkeep │ └ docker-compose.yml </code></pre> <p>Docker に関する部分のみ列挙。</p> <h3 id="docker-compose.yml"><a href="#docker-compose.yml">docker-compose.yml</a></h3> <pre><code class="yml">version: '3.7' volumes: logs: driver: local services: slim: build: context: ./_docker/dockerfiles/ dockerfile: Dockerfile working_dir: /var/www command: php -S 0.0.0.0:8080 -t public environment: docker: "true" ports: - 8080:8080 volumes: - .:/var/www - ./logs:/var/www/logs db: image: mariadb restart: always ports: - 3306:3306 volumes: - ./_docker/mysql/mysql:/var/lib/mysql - ./_docker/mysql/initdb.d:/docker-entrypoint-initdb.d environment: - MYSQL_ROOT_PASSWORD=pwd - MYSQL_DATABASE=test - MYSQL_USER=user - MYSQL_PASSWORD=pwd phpmyadmin: image: phpmyadmin/phpmyadmin volumes: - ./_docker/phpmyadmin/sessions:/sessions - ./_docker/phpmyadmin/conf/phpmyadmin-misc.ini:/usr/local/etc/php/conf.d/phpmyadmin-misc.ini environment: - PMA_ARBITRARY=1 - PMA_HOST=db - PMA_USER=user - PMA_PASSWORD=pwd ports: - 8081:80 </code></pre> <p>変更点は以下。</p> <ul> <li>PHP 用環境: <code>Dockerfile</code> を使ったイメージにカスタマイズ</li> <li>DB: MariaDB のイメージをそのまま利用 <ul> <li><code>environment</code> のパラメータはそのまま使用。アプリケーションや phpMyAdmin 側と動機は取れていないですがひとまずはこれで……</li> </ul></li> <li>phpMyAdmin: これも phpMyAdmin 公式イメージを流用</li> </ul> <h3 id="_docker/dockerfiles/Dockerfile"><a href="#_docker%2Fdockerfiles%2FDockerfile">_docker/dockerfiles/Dockerfile</a></h3> <pre><code class="Dockerfile">FROM php:7-alpine RUN apk --update add curl RUN set -ex \ && apk --no-cache add \ autoconf build-base RUN pecl install xdebug RUN docker-php-ext-enable xdebug RUN docker-php-ext-install pdo_mysql COPY settings/php.ini /usr/local/etc/php/conf.d RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer </code></pre> <p><code>php:7-alpine</code> をベースに Xdebug と Composer を追加。</p> <h3 id="_docker/dockerfiles/settings/php.ini"><a href="#_docker%2Fdockerfiles%2Fsettings%2Fphp.ini">_docker/dockerfiles/settings/php.ini</a></h3> <pre><code class="ini">xdebug.mode=coverage </code></pre> <p>Xdebug でカバレッジを有効化するための設定を追加するため。なお、 <code>Dockerfile</code> で <code>COPY</code> する際のホストマシンでのパスは <strong><code>Dockerfile</code> の存在するディレクトリから下でないと参照できない</strong> という地味な制約があるので <code>dockerfiles</code> ディレクトリをわざわざ掘りました。</p> <h3 id="_docker/phpmyadmin/conf/phpmyadmin-misc.ini"><a href="#_docker%2Fphpmyadmin%2Fconf%2Fphpmyadmin-misc.ini">_docker/phpmyadmin/conf/phpmyadmin-misc.ini</a></h3> <pre><code class="ini">allow_url_fopen = Off max_execution_time = 600 memory_limit = 64M post_max_size = 64M upload_max_filesize = 64M </code></pre> <p>メモリ上限等のカスタマイズ。</p> <hr /> <p>これで今回はカバレッジ出力まで動作することを確認しました。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <h3 id="Xdebug"><a href="#Xdebug">Xdebug</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sachiko-kame/items/bf5480f4d7c751ab28aa">PHPUnitでHTMLコードカバレッジを出すまで(Docker使用) - Qiita</a></li> </ul> <blockquote> <p>Warning: XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set</p> </blockquote> <p>が出たので step3 について実施。</p> <p>なお、今回のケースでは</p> <pre><code>extension=xdebug.so </code></pre> <p>は不要だった。</p> <h2 id="Dockerfile"><a href="#Dockerfile">Dockerfile</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://crudzoo.com/blog/php-docker">Dockerで作るNginx + PHP7 + Xdebug環境 | Crudzoo</a></li> </ul> <p>ほぼこれでOK.</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ucan-lab/items/fbf021bf69896538e515">php-alpineコンテナにxdebugをインストールする時にハマったメモ - Qiita</a></li> </ul> <p>こちらも参照。 <code>php -v</code> で <code>with Xdebug</code> と付いていればOK。</p> <h4 id="Dockerfile の COPY"><a href="#Dockerfile+%E3%81%AE+COPY">Dockerfile の COPY</a></h4> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://scrapbox.io/taka521-tech-notes/%E3%80%90Docker%E3%80%91COPY%E3%81%A7%E6%8C%87%E5%AE%9A%E3%81%95%E3%82%8C%E3%81%9F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AF%E3%80%81Dockerfile%E3%81%8C%E5%AD%98%E5%9C%A8%E3%81%99%E3%82%8B%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%81%8B%E3%82%89%E3%81%AE%E7%9B%B8%E5%AF%BE%E3%83%91%E3%82%B9%E3%81%A7%E3%80%81%E8%A6%AA%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%82%92%E8%A6%8B%E3%82%8C%E3%81%AA%E3%81%84">【Docker】COPYで指定されたファイルは、Dockerfileが存在するディレクトリからの相対パスで、親ディレクトリを見れない - タカの技術ノート</a></li> </ul> <h3 id="Mariadb"><a href="#Mariadb">Mariadb</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://mebee.info/2020/04/07/post-8227/">dockerを使用してmariadbを構築する | mebee</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/tarch710/items/1236a23f7ffde4c512f2">開発環境をDockerにしたら、PDOでcould not find driverが出た - Qiita</a></li> </ul> <blockquote> <p>could not find driver</p> </blockquote> <p>普通に考えたら確かにドライバがないので追加して解決。</p> <h3 id="シェル"><a href="#%E3%82%B7%E3%82%A7%E3%83%AB">シェル</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/yutachaos/items/56dd7ea09d7e2b0d9173">dockerでalpine linux ベースのcontainerに入って、shellを使いたいとき。 - Qiita</a></li> </ul> <p>bash ではなく ash 。 <code>bin/ash</code> 指定。</p> <h3 id="sr -c"><a href="#sr+-c">sr -c</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://genzouw.com/entry/2020/01/28/120014/1910/">docker-compose.ymlのcommandプロパティに複数コマンドを設定する方法 | ゲンゾウ用ポストイット</a></li> </ul> <p>最終的には使わずに済みましたが念のためメモ。</p> arm-band tag:crieit.net,2005:PublicArticle/17975 2022-02-04T22:26:58+09:00 2022-08-07T01:46:25+09:00 https://crieit.net/posts/Linux-messages [Linux] messagesログの日付指定参照 <p>Linuxにおける「/var/log/messages」ファイルのフォーマットは以下のように「日 時 ホスト名 情報」となっている。</p> <pre><code>Feb 16 18:00:10 web sshd[13865]: Accepted publickey for user01 from 192.168.0.2 port 45680 ssh2 </code></pre> <p>問題なのが1桁の日にちの場合以下のように「月(スペース)(スペース)日にち」の表現になっている点だ。</p> <pre><code>Feb 4 21:25:22 ホスト名 情報 </code></pre> <p>「04」ならともかく「(スペース)4」と表現されることにより、<br /> bashスクリプト等で実行日に絞り込みたい場合、以下のコマンドだと絞り込みができない。</p> <pre><code>$ LANG=C date "+%b %d" Feb 04 </code></pre> <hr /> <p>以下のコマンドだと、1桁の日にちも2桁の日にちも処理できる。</p> <pre><code>$ LANG=C date | cut -d " " -f 2-4 | sed -e "s/ ..:.*$//" </code></pre> <p><code>LANG=C date</code>「 Fri Feb 4 22:10:50 JST 2022」のような日付を取得<br /> <code>cut -d " " -f 2-4</code> 「Feb 4 」に絞り込み、ただし、2桁日にちの場合時刻まで取れてしまう。<br /> <code>sed -e "s/ ..:.*$//"</code> 2桁日にちの場合の時刻除去</p> <p>1桁日にちのコマンド実行例</p> <pre><code>$ LANG=C date | cut -d " " -f 2-4 | sed -e "s/ ..:.*$//" Feb 4 </code></pre> <p>2桁日にちのコマンド実行例</p> <pre><code>LANG=C date --date '5 day ago' | cut -d " " -f 2-4 | sed -e "s/ ..:.*$//" Jan 30 </code></pre> <p><strong>注意として変数に入れて使用する場合、変数をダブルクォート(")で囲む必要がありそうだ。<br /> そうしないとスペースの2個が1個で処理してしまう可能性がある。</strong></p> <pre><code>$ LOGDATE=`LANG=C date | cut -d " " -f 2-4 | sed -e "s/ ..:.*$//"` $ echo $LOGDATE Feb 4 echo "$LOGDATE" Feb 4 $ </code></pre> <hr /> <p>ワンライナーでの実行例 (先頭.は表現回避用)</p> <pre><code>.# cat /var/log/messages | grep "`LANG=C date | cut -d " " -f 2-4 | sed -e "s/ ..:.*$//"`" Feb 4 10:0:01 ホスト名 情報 Feb 4 10:0:01 ホスト名 情報 </code></pre> <p>変数定義後の事項例 (先頭.は表現回避用)</p> <pre><code>.# LOGDATE=`LANG=C date | cut -d " " -f 2-4 | sed -e "s/ ..:.*$//"` .# cat /var/log/messages | grep "${LOGDATE}" Feb 4 10:0:01 ホスト名 情報 Feb 4 10:0:01 ホスト名 情報 </code></pre> dsta50 tag:crieit.net,2005:PublicArticle/17916 2022-01-03T23:53:56+09:00 2022-05-05T23:33:42+09:00 https://crieit.net/posts/Linux-ssh [Linux] ssh鍵の自動生成 <p>Linuxの1ユーザの複数サーバ分のssh鍵の自動生成</p> <p>コードブロックに行番号を付加できないかは模索中</p> <p>ファイル名;create_key.sh</p> <pre><code>#!/bin/bash # values KEYSIZE=2048 # script dir DIR_BASE=$(cd $(dirname ${0}); pwd) if [ $# = 0 ] ; then echo -e "Error: No arguments set" echo -e "Usage: $0 [user name] [server01:server02:...]" exit 10 fi USER=$1 SERVERS=`echo $2 | sed -e 's/:/ /g'` USERDIR=${DIR_BASE}/${USER} test ! -d ${USERDIR} && mkdir ${USERDIR} for server in ${SERVERS} do test ! -d ${USERDIR}/${server} && mkdir ${USERDIR}/${server} passphrase=`cat /dev/urandom | tr -dc 'a-kmnpr-zABD-HJL-NQRPT0-9#?_!' | fold -w 8 | head -n 300 | grep -v '^[#?_!]' | grep '[#?_!]' | grep [0-9] | grep -vE '(.)\1' | shuf -n 1` keyfile=${USERDIR}/${server}/${USER} ssh-keygen -f ${keyfile} -t rsa -b ${KEYSIZE} -N ${passphrase} if [ $? = 0 ] ; then echo -e "User: ${USER} ,Server: ${server}, Passphrase: [${passphrase}]" echo -e "## ${server}\nUser:\n${USER}\n\nPassphrase:\n${passphrase}" > ${server}_${USERDIR}/SSHinfo_${USER}.txt else echo -e "Couldn't make the key. -User: ${USER} ,Server: ${server}" fi done </code></pre> <p>実行例</p> <pre><code>$ ./create_key.sh user01 web:mail </code></pre> <p><code>引数1</code> ユーザ名<br /> <code>引数2</code> サーバ名、複数の場合はサーバ名を: (コロン)で区切る</p> <p>説明<br /> 1. スクリプトファイルと同じディレクトリ階層に作成したい ユーザ名のディレクトリを作成する (実行例:user01)<br /> 2. サーバごと以下2-1から2-4を繰り返す<br /> 2-1.1番で作1番で作成したユーザディレクトリ配下にサーバ名のディレクトリを作成する (実行例:web)<br /> 2-2.パスフレーズをランダムの文字列で生成<br /> 2-3.2-2番で生成したパスフレーズをもとに、2-1番で作成したサーバ名のディレクトリに鍵のペアを作成<br /> 2-4.1番で作成したユーザ名のディレクトリにサーバ名、ユーザ名、パスフレーズを記載したファイルを作成</p> <p>2-4で作成したファイル名はSSHinfo_サーバ名_ユーザ名.txt<br /> 例 web_SSHinfo_user01.txt</p> <pre><code>## web User: user01 Passphrase: U4#DvXnH </code></pre> <p>エラー処理を最小限にしているため、予期せぬ事態に対処できてないかも</p> <p>課題 (後日記載)<br /> 複数ユーザに対応するため、引数にリストファイルを指定することで、複数ユーザの指定サーバごとに鍵を作成する</p> <p>memo<br /> psslibでのパスワードのハッシュ化</p> <pre><code> passhash=`python3 -c 'import passlib.hash; print(passlib.hash.sha512_crypt.using(rounds=5000).hash("$passphrase"))'` echo -e "password hash is [${passhash}] " echo -e "\npassword hash\n${passhash}" >> ${server}_${USERDIR}/SSHinfo_${USER}.txt </code></pre> dsta50 tag:crieit.net,2005:PublicArticle/17908 2022-01-01T21:51:05+09:00 2022-05-05T23:31:37+09:00 https://crieit.net/posts/Linux-61d04e3986a0e [Linux] ランダム文字列 <p>Linux環境にてパスワードやパスフレーズ等の設定に使うランダム文字列を生成</p> <p>条件:8文字構成で数字、アルファベット小文字、大文字、記号の使用かつ類似文字を除外<br /> 数字 10文字 (0 1 2 3 4 5 6 7 8 9)<br /> 小文字 23文字 (a b c d e f g h i j k m n p r s t u v w y z)、除外 (l o q)<br /> 大文字 14文字 (A B D E F G H J L M N Q R T)、除外 (C I K O S U V W X Y Z)<br /> 記号 4文字 (# ? _ !)</p> <pre><code>cat /dev/urandom | tr -dc 'a-kmnpr-zABD-HJL-NQRPT0-9#?_!' | fold -w 8 | head -n 300 | grep -v '^[#?_!]' | grep '[#?_!]' | grep [A-Z] | grep [a-z] | grep [0-9] | grep -vE '(.)\1' | uniq | shuf -n 1 </code></pre> <p>省略版 記号と数字のチェックすれば問題なさそう</p> <pre><code>cat /dev/urandom | tr -dc 'a-kmnpr-zABD-HJL-NQRPT0-9#?_!' | fold -w 8 | head -n 300 | grep -v '^[#?_!]' | grep '[#?_!]' | grep [0-9] | grep -vE '(.)\1' | shuf -n 1 </code></pre> <p><code>cat /dev/urandom</code> ランダムな文字列を生成<br /> <code>tr -dc 'a-kmnpr-zABD-HJL-NQRPT0-9#?_!'</code> 条件の含めたい文字<br /> <code>fold -w 8</code> 8文字<br /> <code>head -n 300</code> 行頭から300文字<br /> <code>grep -v '^[#?_!]'</code> 先頭記号の文字列を除く<br /> <code>grep '[#?_!]' | grep [A-Z] | grep [a-z]</code> 条件に合致しているか絞り込み<br /> <code>grep -vE '(.)\1'</code> 繰り返し文字を除く<br /> <code>uniq</code> 念のため重複行の取り除き、後のコマンドで含まれているのでいらない<br /> <code>shuf -n 1</code> ランダムの1行を出力</p> <p>実行例</p> <pre><code>$ cat /dev/urandom | tr -dc 'a-kmnpr-zABD-HJL-NQRPT0-9#?_!' | fold -w 8 | head -n 300 | grep -v '^[#?_!]' | grep '[#?_!]' | grep [0-9] | grep -vE '(.)\1' | shuf -n 1 9eD?8aiG </code></pre> dsta50 tag:crieit.net,2005:PublicArticle/17878 2021-12-21T07:40:58+09:00 2021-12-21T08:00:24+09:00 https://crieit.net/posts/Bash シェルスクリプト(Bashスクリプト)でかんたんな自作言語のコンパイラを書いた <p>シェルスクリプト Advent Calendar 2021<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/shellscript">https://qiita.com/advent-calendar/2021/shellscript</a><br /> の20日目の記事です。</p> <p><a href="https://crieit.now.sh/upload_images/d022495e2d320868f0b13d07c98d3f9861b66f943faf6.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d022495e2d320868f0b13d07c98d3f9861b66f943faf6.png?mw=700" alt="image" /></a></p> <p>Bashがある環境ならどこでも自作言語で書いたプログラムをコンパイルできるようになりました。</p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-bash">https://github.com/sonota88/vm2gol-v2-bash</a></p> <p>いろいろと雑なのですが、アドベントカレンダーの期日があるのでとりあえず公開します。</p> <p>サイズはこんな感じ。</p> <pre><code>$ wc -l {lexer,parser,codegen}.sh lib/*.sh 220 lexer.sh 655 parser.sh 525 codegen.sh 42 lib/common.sh 193 lib/json.sh 284 lib/utils.sh 1919 合計 </code></pre> <h1 id="動かし方の例"><a href="#%E5%8B%95%E3%81%8B%E3%81%97%E6%96%B9%E3%81%AE%E4%BE%8B">動かし方の例</a></h1> <pre><code class="bash">echo ' func add(a, b) { return a + b; } func main() { var one = 1; var result; call_set result = add(one, 2); } ' | ./lexer.sh | ./parser.sh | ./codegen.sh # ↓アセンブリが出力される call main exit label add push bp cp sp bp cp [bp:2] reg_a push reg_a cp [bp:3] reg_a push reg_a pop reg_b pop reg_a add_ab cp bp sp pop bp ret label main push bp cp sp bp sub_sp 1 cp 1 reg_a cp reg_a [bp:-1] sub_sp 1 cp 2 reg_a push reg_a cp [bp:-1] reg_a push reg_a _cmt call~~add call add add_sp 2 cp reg_a [bp:-2] cp bp sp pop bp ret # (snip) </code></pre> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></p> <p>Bashスクリプト版のベースになっているバージョンは <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/62">tag:62</a> のあたり</p> <p><自作言語処理系の説明用テンプレ></p> <p>自分がコンパイラ実装に入門するために作った素朴なトイ言語とその処理系です。簡単に概要を書くと下記のような感じ。</p> <ul> <li>小規模: コンパイラ部分は 1,000 行程度</li> <li>pure Ruby / 標準ライブラリ以外のライブラリ不要</li> <li>x86風の自作VM向けにコンパイルする</li> <li>ライフゲームのために必要な機能だけ <ul> <li>変数宣言、代入、反復、条件分岐、関数定義</li> <li>演算子: <code>+</code>, <code>*</code>, <code>==</code>, <code>!=</code> のみ(優先順位は <code>(</code> <code>)</code> で明示)</li> <li>型なし(値は符号付き整数のみ)</li> </ul></li> <li>作ったときに書いた備忘記事 <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/f9cb3fc4a496b354b729">RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/2b95378b43a22109513c">Rubyでかんたんな自作言語のコンパイラを作った</a></li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/vm2gol_v2_additional_features">本体には含めていない後付けの機能など</a> <ul> <li>真偽値リテラル / break / if/else / 単項マイナス / Racc などを使って書いたパーサの別実装</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">Ruby 以外の言語への移植</a> <ul> <li>コンパイラ部分のみ</li> <li>Python, Java, TypeScript など、2021-12-19 の時点では 20言語</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1e683276541cf1b87b76">セルフホスト版</a></li> <li>製作過程を知りたい場合は<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/234516">製作メモ</a>を見てください</li> </ul> <p><説明用テンプレおわり></p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <h2 id="文字列"><a href="#%E6%96%87%E5%AD%97%E5%88%97">文字列</a></h2> <p>下記の記事のおかげでなんとかなりました。ありがとうございます 🙏</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/richmikan@github/items/6b763519a28b4ce40031">シェルスクリプトはバイナリを扱えない。さてどうしよう…… - Qiita</a></p> <p>バイト単位で16進数表現との相互変換さえできてしまえば、あとは煮るなり焼くなり思いのまま、です。</p> <h2 id="データ構造"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0">データ構造</a></h2> <pre><code class="json">[ 123, "fdsa", [ 11, "FDSA", [] ] ] </code></pre> <p>のような、リストの要素として整数、文字列、リストを持たせることができる再帰的なデータ構造が必要です。Bashスクリプトの配列は単純な値を入れる分にはいいのですが、ネストしたリストを扱うことができなさそうでした。</p> <p>(※ いつもならこういうことをやりたくなった段階で別の言語で書くことを検討しますが、今回は Bashスクリプトで書くこと自体が目的です)</p> <p>そこで、ひとまず下記のようにしました。もっと良いやり方がある気がします。</p> <p>リストの要素にあたるデータの表現はこんな感じ。</p> <pre><code class="bash"># 整数 "int:-123" # 文字列 "str:fdsa" # リスト "list:1" </code></pre> <ul> <li>先頭に型を表すタグを付けた1行の文字列</li> <li>文字列の値(<code>str:...</code> の <code>...</code> の部分)は改行を含まない(レキサで捨てる)ため、改行についての考慮は不要</li> <li><code>list:</code> の後に続く数字はリストID</li> </ul> <p>たとえば <code>["+", 1, -2]</code> というリストは次のように3行の文字列で表せます。1行がリストの要素1つに対応します。</p> <pre><code class="bash">"str:+ int:1 int:-2 " </code></pre> <p>この文字列をグローバルな配列変数に入れて管理する形にしました。要素のインデックスがリストIDに対応します。</p> <p>たとえば、入れ子のあるリスト <code>["+", 1, ["*", 2, 3]]</code> は次のようになります。</p> <pre><code class="bash"># GLOBAL[0] "str:* int:2 int:3 " # GLOBAL[1] "str:+ int:1 list:0 " </code></pre> <p>パフォーマンスの問題は別途ありますが、要するに下記のような操作ができるインターフェイスが用意できればOK。</p> <ul> <li>リストの要素数を求める</li> <li>リストからn番目の要素を読み書きする</li> <li>リストの要素の型を判別する</li> <li>リストの要素の値を取りだす</li> </ul> <h2 id="関数からの値の返却"><a href="#%E9%96%A2%E6%95%B0%E3%81%8B%E3%82%89%E3%81%AE%E5%80%A4%E3%81%AE%E8%BF%94%E5%8D%B4">関数からの値の返却</a></h2> <p>関数 <code>myfunc</code> から標準出力に出力して <code>result="$(myfunc)"</code> のようにコマンド置換で受け取る方法だと不都合な点が2つあります。</p> <ul> <li>(1) 末尾の改行の有無を制御しにくい <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ko1nksm/items/7c4040c6b6f3fac14db7">シェルスクリプトにはコマンド出力を変数に入れると末尾の改行が全部消えてしまう罠がある! - Qiita</a> に書かれている通り</li> </ul></li> <li>(2) 呼び出した関数内でグローバル変数を更新できない <ul> <li>コマンド置換だとサブシェルになるので</li> </ul></li> </ul> <p>ちょっと悩みましたが、結局下記の方式に落ち着きました。</p> <ul> <li>関数からの戻り値の受け渡し用のグローバル変数 <code>RV1</code> を用意する</li> <li><code>$(...)</code> で囲まずに普通に関数を呼び出す</li> <li>関数側では、返したい値を <code>RV1</code> に代入する</li> <li>呼び出し元では、関数呼び出しの直後に <code>RV1</code> から移し替える</li> </ul> <p>単純な例として、文字列を2つ受け取って連結した文字列を返す関数。</p> <pre><code class="bash"># return value RV1= myfunc() { RV1="${1}${2}" } myfunc "foo" "bar" retval="$RV1" </code></pre> <p>こうですね。単にこのパターンで書けばよいだけなので頭は使わなくてよいです。無心に書いていくとコンパイラができあがります。並行処理とかやってないですし、関数から戻った直後で受け取る約束さえ守っていれば問題は起こりません。</p> <p>全然スマートじゃなくてなんじゃこりゃって感じのソリューションですけど、この方法にも良いところがあって、<code>RV2</code>, <code>RV3</code>, ... と増やすことで複数の値をいくらでも返すことができます。あと、コマンド置換よりはオーバーヘッドが小さいかもしれません(未確認)。</p> <p>関数の中でグローバル変数を書き換える実際の例としてはリストの生成処理があります。ヒープにリスト用の領域を確保して、リストを指し示すポインタを返すようなイメージです。</p> <pre><code class="bash">new_list() { new_gid local self_=$RV1 GLOBAL[$self_]="" RV1=$self_ } new_list list1_=$RV1 </code></pre> <p>Ruby で書くとしたらこんな感じ。</p> <pre><code class="ruby">def new_list self_ = new_gid() # 新しいインデックスを採番 $GLOBAL[self_] = "" return self_ end list_ = new_list() </code></pre> <hr /> <p>というわけで、かんたんなコンパイラを書くことにより、Bashスクリプトにおける</p> <ul> <li>文字列処理</li> <li>データ構造(入れ子のあるリスト)</li> <li>関数からの値の返却</li> </ul> <p>についてのノウハウが新たに得られました。めでたしめでたし。</p> <h1 id="この記事を読んだ人は(たぶん)こちらも読んでいます"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E4%BA%BA%E3%81%AF%EF%BC%88%E3%81%9F%E3%81%B6%E3%82%93%EF%BC%89%E3%81%93%E3%81%A1%E3%82%89%E3%82%82%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%84%E3%81%BE%E3%81%99">この記事を読んだ人は(たぶん)こちらも読んでいます</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1482e236054ad3edfdf5">Perlでかんたんな自作言語のコンパイラを書いた</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/54c65e97aa563009137e">なでしこ3でかんたんな自作言語のコンパイラを書いた</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/31c127c42a9722892aa8">素朴な自作言語のコンパイラをLibreOffice Basicに移植した</a></p> sonota486 tag:crieit.net,2005:PublicArticle/17861 2021-12-17T09:13:56+09:00 2021-12-25T10:12:16+09:00 https://crieit.net/posts/bash-2 bash で標準出力と標準エラーを 2つのコマンドに出し分ける <p>本記事は <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/shellscript">シェルスクリプトのカレンダー | Advent Calendar 2021 - Qiita</a> 17日目の記事だ。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/shellscript" target="_blank" rel="noopener">シェルスクリプトのカレンダー | Advent Calendar 2021 - Qiita</a></p> <p>殆どカレンダーが埋まってなかったので、思いついたネタで埋めちゃえ埋めちゃえ。</p> <p>今回は、 bash 系列 (bash, zsh 等) の <a target="_blank" rel="nofollow noopener" href="https://linuxjm.osdn.jp/html/GNU_bash/man1/bash.1.html#lbBE">プロセス置換 (process substitution)</a> 機能の話だ。</p> <p>このプロセス置換は POSIX 互換の機能では無いため、以降の例は ash 系列 (busybox hush (ash), dash 等) では利用できない。</p> <h2 id="bash のプロセス置換"><a href="#bash+%E3%81%AE%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E7%BD%AE%E6%8F%9B">bash のプロセス置換</a></h2> <p>bash 系の プロセス置換 という機能と、 tee コマンドを組み合わせて、 同時に複数のコマンドにパイプできるらしい。</p> <p>詳しい説明を読んでも、正直頭がさっぱり追いつかないが…</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/angel_p_57/items/fd1d9a10e2f4aba17669" target="_blank" rel="noopener">Linuxでのプロセス置換 #Linux - Qiita</a></p> <p>要は、 引数部分でファイルを指定するべき場所で、コマンドの標準入出力を代用できる機能ということか。</p> <p>例えば、 "<code>cat <(ls ./a) <(ls ./b)</code>" とすれば、 "<code>ls ./a</code>" の内容と "<code>ls ./b</code>" の内容が連結されて出力されるし、<br /> "<code>command0 | tee >(command1) | command2</code>" とすれば、 command0 の標準出力が、 command1 の標準入力と、 command2 の標準入力両方に渡される。<br /> また、「ファイルの読み書きの代替」となるため、 "<code>command0 2> >(command1)</code>" のようにリダイレクト先のファイル名の替わりにプロセス置換を使えば、標準エラー<strong>だけ</strong>を command1 の標準入力に渡すことができる。</p> <p>ちょっとその尖った使い所を考えてみる。</p> <hr /> <p>例えばこんな、標準出力と標準エラーを吐き出すスクリプトがあったとしよう。</p> <pre><code class="bash">$ cat <<'EOF' > ./testecho.sh #!/bin/bash echo "stdout1" >&1 echo "stderr1" >&2 echo "stdout2" >&1 echo "stderr2" >&2 EOF $ chmod u+x ./testecho.sh </code></pre> <p>このスクリプトを実行し、 標準エラーだけ command2 に渡して、 標準出力は command1 に渡したい場合、 プロセス置換を使うと以下のようにできる。</p> <pre><code class="bash">$ ./testecho.sh 2> >(command2) | command1 # -&gt; 各コマンドの入力 # command1: # stdout1 # stdout2 # command2: # stderr1 # stderr2 </code></pre> <p>ここで更に、 標準出力と標準エラーの両方を command2 に渡して、 標準出力だけを command1 に渡したい場合、 ちょっと複雑になるが以下のようにして実現できる。</p> <pre><code class="bash">$ ./testecho.sh 2>&1 > >(tee >(command1)) | command2 # -&gt; 各コマンドの入力 # command1: # stdout1 # stdout2 # command2: # stdout1 # stderr1 # stderr2 # stdout2 </code></pre> <p>少し複雑なので分解して考えてみよう。</p> <p><a href="https://crieit.now.sh/upload_images/351d9041acf3d7f89470b18a3548b83961bb919f06ca5.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/351d9041acf3d7f89470b18a3548b83961bb919f06ca5.png?mw=700" alt="bash_tee_stdout_and_err-00.png" /></a></p> <ol> <li>まずは <code>testecho.sh</code> のリダイレクト部分 (赤枠) を考える。 <ol> <li>リダイレクトを複数並べる場合は左から評価されるが、後ろに書いたリダイレクトで上書きされるような動きをするので、右から順番にリダイレクトされると考えるとわかりやすい。 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup><br /> このため、まずは (黄色下線部) の部分を見てみよう。<br /> この部分は、標準出力をファイルにリダイレクトしているだけ (つまり、只の "<code>> ファイルパス</code>") の表記だ。<br /> ただ、ファイルパスの代わりにプロセス置換 ("<code>>(cmd_list)</code>") が使われており、 標準出力が <code>tee</code> の標準入力へ書き込まれている。 <ol> <li>さらに、 <code>tee</code> のファイル出力もまた、プロセス置換を使って <code>command1</code> の標準入力に渡される。<br /> <strong>結果的に、 <code>testecho.sh</code> の標準出力だけが、 <code>command1</code> の標準入力に渡されることになる。</strong></li> <li>次に、 <code>tee</code> の標準出力のほう、 これは <code>./testecho.sh</code> を実行した標準出力に戻ってくる。</li> </ol></li> <li>さて、 <code>testecho.sh</code> のリダイレクトに話を戻すと、 その次のリダイレクト (水色下線部) の "<code>2>&1</code>" によって、 <code>tee</code> の標準出力と <code>./testecho.sh</code> の標準エラーが、標準出力側に統合される。</li> </ol></li> <li>最後に、 その統合された標準出力が、 パイプで <code>command2</code> に渡される。<br /> <strong>結果的に、 <code>testecho.sh</code> の標準出力と標準エラーの両方が、 <code>command2</code> の標準入力に渡されることになる。</strong></li> </ol> <p>ちなみに、 ./testecho.sh の標準出力と標準エラーの出力速度が早いと、上記の出力例のように command2 に渡される標準出力と標準エラーが順不同になってしまう。</p> <p>なお、以下のようにやっても同じ結果になるはずだ。</p> <pre><code class="bash">$ ./testecho.sh > >(command2) 2>&1 > >(tee >(command1)) </code></pre> <hr /> <h2 id="実用例"><a href="#%E5%AE%9F%E7%94%A8%E4%BE%8B">実用例</a></h2> <p>例えば、以下のようにすると、 コマンドの標準出力だけメールを出しつつ、メールの内容と 標準エラーの両方を journal に書き込む事ができる。</p> <pre><code>./testecho.sh 2>&1 > >(tee >(/usr/sbin/sendmail [email protected])) | /usr/bin/systemd-cat </code></pre> <p>ただ、どうせ journal に記録するなら、 <code>-t</code> オプションを使って、以下の 3 つを識別子で分けて記録したほうが良いかもしれない。</p> <ul> <li>./testecho.sh の標準エラー</li> <li>./testecho.sh の標準出力</li> <li>sendmail の標準出力&エラー</li> </ul> <pre><code>./testecho.sh 2> >(/usr/bin/systemd-cat -t cmderr) > >(tee >(/usr/bin/systemd-cat -t mailout /usr/sbin/sendmail -v [email protected]) | /usr/bin/systemd-cat -t cmdout) </code></pre> <p>うーん、 <strong>ここまで来ると初見で動作を理解できる気がしない。</strong></p> <p><strong>参考:</strong></p> <ul> <li>https://tellme.tokyo/post/2020/02/07/tee-command/</li> <li>https://www.greptips.com/posts/189/</li> </ul> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p>記事の初稿でリダイレクトの参照順の説明が誤っていたので訂正。ただ、詳しい仕組みは難しくて説明しきれないのでググって。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> advanceboy tag:crieit.net,2005:PublicArticle/15475 2019-10-12T12:27:26+09:00 2019-10-12T12:27:26+09:00 https://crieit.net/posts/IPA IPAのネットワークスペシャリスト試験の過去問一括ダウンロードスクリプト <p>IPAのネットワークスペシャリスト試験の過去問を一括ダウンロードするスクリプト。<br /> 平成16年~平成20年は、午前がまとまっているので、午後の問題のみ。</p> <h3 id="スクリプトはこんな感じ"><a href="#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AF%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98">スクリプトはこんな感じ</a></h3> <pre><code class="shell">#!/bin/bash # ***************************************************************************** # * ネットワークスペシャリスト試験の過去問一括ダウンロードスクリプト # ***************************************************************************** first_year=16 # 平成16年から last_year=30 # 平成30年まで base_url='https://www.jitec.ipa.go.jp/1_04hanni_sukiru' dl_dir='dist' dl_pdf() { year_label="${1}h${2}" fname="$3" url="${base_url}/mondai_kaitou_${year_label}_2/${year_label}a_${fname}.pdf" echo "#### DOWNLOAD: ${url}" wget ${url} -P "./${dl_dir}" sleep 1 # 1秒待つ } rm -rf ${dl_dir} mkdir ${dl_dir} for i in `seq ${first_year} ${last_year} | sort -r`;do year="$((1988 + i))" year2="${i}" echo "${year} - ${year2}" if [ $year2 -gt 21 ]; then dl_pdf "$year" "$year2" 'nw_am2_qs' dl_pdf "$year" "$year2" 'nw_am2_ans' dl_pdf "$year" "$year2" 'nw_pm1_qs' dl_pdf "$year" "$year2" 'nw_pm1_ans' dl_pdf "$year" "$year2" 'nw_pm1_cmnt' dl_pdf "$year" "$year2" 'nw_pm2_qs' dl_pdf "$year" "$year2" 'nw_pm2_ans' dl_pdf "$year" "$year2" 'nw_pm2_cmnt' elif [ $year2 -gt 18 ]; then dl_pdf "$year" "$year2" 'nw_pm1_qs' dl_pdf "$year" "$year2" 'nw_pm1_ans' dl_pdf "$year" "$year2" 'nw_pm1_cmnt' dl_pdf "$year" "$year2" 'nw_pm2_qs' dl_pdf "$year" "$year2" 'nw_pm2_ans' dl_pdf "$year" "$year2" 'nw_pm2_cmnt' else dl_pdf "$year" "$year2" 'nw_pm1_qs' dl_pdf "$year" "$year2" 'nw_pm1_ans' dl_pdf "$year" "$year2" 'nw_pm2_qs' dl_pdf "$year" "$year2" 'nw_pm2_ans' fi done </code></pre> <p>こんな感じで実行すると...</p> <pre><code class="console">$ bash download_pdf_nw.sh 2>&1 </code></pre> <p>こんな感じにdist配下にPDFをダウンロードしてきます。</p> <p><img width="457" alt="スクリーンショット 2019-10-12 12.22.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/576e7a19-7547-77f1-446d-750ae25aa018.png"></p> <p>あとは、好きなのを印刷すればOK(<em>´ω`</em>)</p> <p>システムアーキテクトやデータベーススペシャリスト版はこちら。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2018/10/26/155638">システムアーキテクト試験の過去問一括ダウンロードスクリプト - くらげになりたい。</a><br /> ・<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/04/07/130258">データベーススペシャリスト試験の過去問一括ダウンロードスクリプト - くらげになりたい。</a></p> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15473 2019-10-11T01:39:47+09:00 2019-10-11T01:39:47+09:00 https://crieit.net/posts/6417df54e9cc7b5492fe9b8d2af9e43e パイプで受け取った標準入力を列ごとに読みこんで配列っぽく処理したい <p>最近、古いシェルスクリプトをリファクタリングしたので、そのときに使ったテクニックをここに残しておく。</p> <p>コマンドをパイプでつないで、最終的に出力を行単位で読み込みたいときがある。例えば、以下の<code>ls</code>コマンドの出力から一番ファイルサイズが大きいやつを選びたいとする。</p> <pre><code class="console">$ ls /tmp/ total 28 ... drwx------ 2 root wheel 64 10 8 01:25 KSDownloadAction.dFCyHsIUZR -rw-r--r-- 1 root wheel 0 10 9 23:35 wetrsmuoutpDmR5aR -rw-r--r-- 1 root wheel 0 10 9 23:36 wetrsmuoutpalIXOW ... </code></pre> <p>レガシー(というかマナーが悪い)なコードだとこんな感じになる。</p> <pre><code class="sh">#!/usr/bin/env bash set -eu OLD_IFS="${IFS}"; IFS=$'\n' max_size=0 max_filename= ls_outpu="$(\ls -l /tmp/ | sed -e '1d' | grep -v '^d')" for output in ${ls_outpu} do filesize="$(echo "${output}" | awk '{print $5}')" filename="$(echo "${output}" | awk '{print $9}')" if [ "${filesize}" -gt "${max_size}" ]; then max_size="${filesize}" max_filename="${filename}" fi done IFS="${OLD_IFS}" </code></pre> <p>やりたいことはタイトルにあるように「パイプで受け取った標準入力を列ごとに読みこんで配列っぽく処理したい」なんだけど、上のコードはいくつか良くない点がある。</p> <ol> <li><code>IFS</code>を一時的に置き換えている。</li> <li>空白区切りの行から特定位置の文字列を取り出すために<code>awk</code>を使っている。</li> <li><strong>数字</strong>を比較している。</li> </ol> <p>1つずつ説明する。</p> <h2 id="問題点と解決法"><a href="#%E5%95%8F%E9%A1%8C%E7%82%B9%E3%81%A8%E8%A7%A3%E6%B1%BA%E6%B3%95">問題点と解決法</a></h2> <h3 id="IFSを一時的に置き換えている。"><a href="#IFS%E3%82%92%E4%B8%80%E6%99%82%E7%9A%84%E3%81%AB%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%81%A6%E3%81%84%E3%82%8B%E3%80%82">IFSを一時的に置き換えている。</a></h3> <p>これは古いシェルスクリプトだとよく見かけるけど、このやり方はメンテナンスが非常にやりにくくなり、本当に危ないのでやめたほうが良い。<br /> まず、<code>IFS</code>はshellにおいて、文字列の<em>区切り</em>を示すもの。これを変えることで、文字列の区切りを変えることが出来る。</p> <p>通常、<code>IFS</code>は半角スペース、タブ文字、改行の3つである。ここでは<code>ls -l</code>の出力を行ごとに読み込むために文字列の区切りを改行区切りにしている(そうしなければ、出力を1行ごとに列単位で扱うことになってしまう)。そして処理が完了したら<code>IFS</code>を元に戻している。</p> <p>これ、最初に書いた人は良いけど、別の人がコードを保守するときに、この部分を見落としがちなのでバグが発生しやすくなるんだよね。コードを読む人は途中で<code>IFS</code>が変わったことを常に意識しながら読むことを強いられるので、コードの保守性が著しく悪くなる。</p> <p>よって、<code>IFS</code>の置き換えは良くないのでやめましょう。ではどうすれば良いのかと言うと、ヒアドキュメントを使って<code>read</code>で読み込めば良い。</p> <pre><code class="sh">while read line do ... done <<< "$(ls -l /tmp/ | sed -e '1d' | grep -v '^d')" </code></pre> <p>すこし見づらくなるけど、こうすることで<code>IFS</code>の置き換えが不要になり、行ごとに変数<code>line</code>の中に文字列が格納されるようになる。</p> <p>もしかしたらこうしたほうが良いかも?と思う人がいるかもしれない。</p> <pre><code class="sh">ls -l /tmp/ | sed -e '1d' | grep -v '^d' | while read line do ... done </code></pre> <p>べつにこれでも良いのだけど、パイプで繋がれた処理はサブシェルで起動するということを思い出してほしい。今回のようにwhileのループ文の中で、事前に定義した変数を読み込みたい場合、つまり、以下のような場合、</p> <pre><code class="sh">max_size=0 max_filename= ls -l /tmp/ | sed -e '1d' | grep -v '^d' | while read line do filesize="$(echo "${output}" | awk '{print $5}')" filename="$(echo "${output}" | awk '{print $9}')" if [ "${filesize}" -gt "${max_size}" ]; then max_size="${filesize}" max_filename="${filename}" fi done </code></pre> <p>ループ文中の<code>max_size</code>と<code>max_filename</code>は先に定義したものとは別の変数として扱われる。その上、サブシェルなのでループ文を抜けると、その中にあった変数たちは参照できなくなる。なぜなら、サブシェルはメインのシェルとは違う世界なので。よって、パイプでつなぐのでなく、ヒアドキュメントで入力を受け取るようにしている。</p> <p>もちろん、サブシェルで起動しても問題なければパイプで繋げばいいと思うけど、ヒアドキュメントを使えばサブシェルの起動を1つ減らせるので、リソースやパフォーマンスの面からもオススメしたいやり方だったりする。</p> <h3>空白区切りの行から特定位置の文字列を取り出すために<code>awk</code>を使っている。</h3> <p><code>awk</code>って便利なのでよく使われているんだけど、大抵の場合、空白切りの文字列の特定の位置の文字を取り出すときに使われることが多い。手元のコマンドラインで使うとかだったら良いんだけど、データ処理とかバッチ処理のスクリプトで使っているとしたら、なるべく次に紹介する方法で置き換えたほうが良い。<code>awk</code>は重いし、思わず色々awkの中に処理を詰め込みがちになってしまうから。</p> <p>オススメのリファクタリングとしては、<code>set</code>を使うこと。<code>set</code>というのは、よくシェルスクリプトの冒頭で<code>set -eu</code>とかやると思うんだけど、その<code>set</code>。これはbuilt-in関数なので<code>awk</code>と違って別途にプロセスがforkされることがなくて軽い。</p> <p>で、どういうふうに<code>set</code>を<code>awk</code>の代わりに使うのかというと、以下のようになる。</p> <pre><code class="sh">... set -- ${output} filesize="${5}" filename="${9}" ... </code></pre> <p>これだけ。スッキリしてわかりやすくなったと思う。解説は<code>man set</code>で見れば良いと思うけど、ここに引用しておく。</p> <blockquote> <p>Any arguments remaining after option processing are treated as values for the positional parameters and are assigned, in order, to $1, $2, ... $n.</p> </blockquote> <p><code>--</code>については以下の引用を参考。<code>grep</code>とかでもよく使うので知っている人は多いと思う。</p> <blockquote> <p>-- If no arguments follow this option, then the positional<br /> parameters are unset. Otherwise, the positional parame-<br /> ters are set to the args, even if some of them begin with<br /> a -.</p> </blockquote> <p>つまり、<code>set -- foo bar baz</code>とやると、それぞれが<code>$1</code>, <code>$2</code>, <code>$3</code>に格納される。Pythonで言うところの<code>*args</code>みたいな可変長引数みたいな感じで位置引数として利用することが出来る。これにより、もう<code>awk</code>を使ってわざわざ<code>echo</code>してパイプで…みたいなことをしなくて済む(もちろん<code>IFS</code>を変更していないことが前提である)。</p> <p>副作用としては、シェルスクリプトに渡された位置引数を上書きすることになるので、そういった引数は事前に別の変数に格納しておくのを忘れないこと。</p> <h3><strong>数字</strong>を比較している</h3> <p>これはなんのこっちゃと思うかもしれないけど、<code>-gt</code>や<code>-ne</code>は本来は<strong>数値</strong>を比較するためのものであって<strong>数字</strong>を比較するものではない。つまり、文字列のままで数字を扱うのは危ないのでやめましょうということ。ではどうするかというと、以下のようにすれば良い。</p> <pre><code class="sh"> if [ $((filesize)) -gt $((max_size)) ]; then </code></pre> <p>シェルスクリプトで足し算とかするときに<code>$((foo + bar))</code>みたいなことをするとおもんだけど(もし<code>expr</code>を使っているなら即刻書き換えましょう)、上のようにすることで数字を数値にすることが出来る。これの何が良いかと言うと、もし変数の中身がアルファベットのような数字ではない文字だった場合はゼロとして扱われる。</p> <pre><code class="console">$ a="aaa" $ echo $((a)) 0 </code></pre> <p>状況によるが、変数が数字でなければゼロとして扱って差し支えない場合はこのようにすることで余計な数字判定の式を省くことが出来る。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>ということで、ここまでの内容をまとめると以下のようになる。</p> <pre><code class="sh">max_size=0 max_filename= while read -r line do set -- ${line} filesize="${5}" filename="${9}" if [ "$((filesize))" -gt "$((max_size))" ]; then max_size="${filesize}" max_filename="${filename}" fi done <<< "$(\ls -l /tmp/ | sed -e '1d' | grep -v '^d')" </code></pre> <p>行数はほとんど変わらないけど、最初の方と比べるとわかりやすくなったかなと思う。特別な処理とかないし。ちなみに、もっとも大きいファイル名を取得するだけなら<code>ls -S | head -1</code>で一発で取れるので、わざわざこんなことをしなくて良い。</p> shige tag:crieit.net,2005:PublicArticle/15466 2019-10-08T16:56:35+09:00 2019-10-08T16:56:35+09:00 https://crieit.net/posts/Nuxt-js-Vue Nuxt.jsで未利用のVueコンポーネントを探すシェルスクリプト <p>最近Nuxt.jsで<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">Webアプリ</a>を作ってるけど、<br /> 度重なる改修でVueコンポーネントが乱立。。</p> <p>使ってないのもたくさんありそうなので、調べるスクリプトを作ってみた。</p> <h3 id="スクリプトはこんな感じ"><a href="#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AF%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98">スクリプトはこんな感じ</a></h3> <pre><code class="sh">#!/bin/bash # vueコンポーネントの一覧を取得 FILES=`find components -name "*.vue"` for i in $FILES; do # 全体からコンポーネントをインクルードしている行の数を取得 NUM=`grep -r "$i" * | wc -l | sed -e "s:[^0-9]*::g"` # ファイル名と見つけた件数を表示 echo "** ${NUM}: ${i}" # grepした結果を表示(確認用) grep -r "$i" * echo "" done </code></pre> <p>こんな感じで出力されるので、<code>** 0:</code>で始まるコンポーネントを削除していけばOK。</p> <pre><code class="text">** 1: components/Hero.vue pages/index.vue:import Hero from "~/components/Hero.vue"; ** 0: components/OgpUserStock.vue ** 0: components/Card.vue ** 3: components/atom/TotalNumBooks.vue pages/clip/_uid.vue:import TotalNumBooks from "~/components/atom/TotalNumBooks.vue"; pages/read/_uid.vue:import TotalNumBooks from "~/components/atom/TotalNumBooks.vue"; pages/stack/_uid.vue:import TotalNumBooks from "~/components/atom/TotalNumBooks.vue"; </code></pre> <p>確認用にgrepの結果を出しているけど、コメントアウトしたり、<br /> NUMが0件のときは、結果を表示しないようにすればいい感じ。</p> <p>もしくは、<code>| grep "** 0:"</code>で結果をgrepするとかでもいいかも。</p> <h3 id="appとかディレクトリを変えてる場合"><a href="#app%E3%81%A8%E3%81%8B%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%82%92%E5%A4%89%E3%81%88%E3%81%A6%E3%82%8B%E5%A0%B4%E5%90%88">appとかディレクトリを変えてる場合</a></h3> <p>こんな感じにapp配下とかに場所を変えている場合は、</p> <pre><code>app/ pages/ components/ </code></pre> <p>こんな感じで、findする場所を変えればOK♪</p> <pre><code class="sh">#!/bin/bash FILES=`find app/components -name "*.vue" | sed 's:app/::g'` for i in $FILES; do NUM=`grep -r "$i" app/* | wc -l | sed -e "s:[^0-9]*::g"` echo "** ${NUM}: ${i}" grep -r "$i" app/* echo "" done </code></pre> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15003 2019-05-20T18:32:12+09:00 2019-05-20T22:20:07+09:00 https://crieit.net/posts/Git-Revert Git Revert 複数コミットをまとめて取り消すときにコミットメッセージを設定する <p>「ここからここまでのコミット」と範囲を指定したrevertをして、いい感じのコミットメッセージを作る方法です。</p> <p>複数のコミットをrevertしたい場合は、単に複数のコミットを指定するか、オプション<code>--no-commit</code>でrevertコマンドを繰り返した後に<code>revert --continue</code>して確定します。どちらの場合も、コミットメッセージをコマンドラインで指定することができません。オプション<code>-e</code>はコミットメッセージを編集するためのエディタを開くもので、メッセージをコマンドラインで指定できません。*コマンドリファレンス <a target="_blank" rel="nofollow noopener" href="https://git-scm.com/docs/git-revert">Git - git-revert Documentation</a></p> <p>メッセージを指定するには、オプション<code>--no-commit</code>を実行した後に通常のコマンド<code>git commit</code>をオプション<code>-m</code>と共に実行します。from: <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/27507977/specify-commit-message-with-revert-continue">stack overflow</a></p> <p>ところで、gitには「ここからここまでのコミット」を表す記法があります。例えば、<code>master..HEAD</code>の「<code>..</code>」みたいなやつ。詳しくはここに書いてあります、<a target="_blank" rel="nofollow noopener" href="https://git-scm.com/book/ja/v2/Git-のさまざまなツール-リビジョンの選択#コミットの範囲指定">Git Book コミットの範囲指定</a>。revertコマンドでは、この記法で複数コミットを指定できます。</p> <p>以下のコマンドではbashの文字列展開を使って、すべての取り消し対象コミットのメッセージの先頭行を結合したテキストを作ります。</p> <pre><code class="bash">$ ref=[ここまで戻したいコミット、タグ] $ git revert --no-commit $ref..HEAD $ git commit -m "Revert to $ref" -m "$(eval "git log --pretty=oneline $ref..HEAD")" # 確認 $ git diff $ref HEAD </code></pre> <ul> <li><code>revert --no-commit</code>で、チェリーピッキング <ul> <li><code>revert --continue</code>するとメッセージが指定できない</li> </ul></li> <li>commit -mしてしまえばいい <ul> <li><code>-m</code>オプションを複数書いたら改行して追加される</li> </ul></li> <li>bashで入れ子の文字列展開には<code>eval</code>を使う <ul> <li><code>"$(eval "$var")"</code></li> </ul></li> </ul> あぜち(おばあちゃん)