そのshの正体は
busyboxのshはashで配列が使えないという話です。
業務でシェル芸的ワンライナーを作ることが多くなってきてたのですが、どハマリしたのがshがbashではない環境。
具体的にはESXiのデフォルトシェルはどうやらbusyboxのashらしく、配列が使えないという話。
■そのシェルなんでしょう
今使ってるシェル、というかshで呼び出されるシェルはwhichとlsで分かるようです。
・Archの場合
$ which sh /usr/bin/sh $ ls -l /usr/bin/sh lrwxrwxrwx 1 root root 4 6月 4 17:54 /usr/bin/sh -> bash $ ls -l /usr/bin/bash -rwxr-xr-x 1 root root 866600 6月 4 17:54 /usr/bin/bash
shは/usr/bin/shで、この実体はbashへのシンボリックリンクになってます。
念の為/user/bin/bashをls -lしてみると、こちらは実行権限付きのファイルになってるので、これが実体。
・Debian(9.5)の場合
$ which sh /bin/sh $ ls -l /bin/sh lrwxrwxrwx 1 root root 4 1月 24 2017 /bin/sh -> dash $ which sh /bin/sh $ ls -l /bin/dash -rwxr-xr-x 1 root root 117208 1月 24 2017 /bin/dash
同じように調べてみると、shは/bin/shを呼び出して、/bin/shはdashへのシンボリックリンクになっています。
念の為/bin/bashをls -l してみると、こちらが実行権限付きのファイルになっています。
ちなみにDebianでもbashは入ってるので、明確にbashを指定すればbashでしか通じない文法のシェルでも問題なく動くでしょう。
$ ls -l /bin/dash -rwxr-xr-x 1 root root 117208 1月 24 2017 /bin/dash
実はシェルは他にもいろいろあるので、気になる人はこの辺見てください。(ちょっと情報古いけど)
シェル
■実は配列が使えないシェルがある
これがドハマったところでした。
実はシェルの配列ってbashの機能だったんですね。
具体的にはESXiのコマンドラインで、コマンド結果を配列に突っ込んで、順に取り出しながら代入してうんたらかんたら、みたいなことをワンライナーで書いてたときにそれは起こりました。
sh: syntax error: unexpected "("
シンタックスエラー??
書き方が悪いと思って調べながら何度修正してもだめ。というか、以前にRedHatLinux上で動かしたワンライナー配列のコマンドをもとにして書いてたので、本当に迷宮入りしそうでした。
が、実はESXiのシェルって/bin/busybox ashが実体だったらしいです。(busyboxのデフォルトシェルはashらしい。)
dash,bash,busyboxすべて入っているDebianで試してみましょう。
ashに変更
tmin@tminserver:~$ /bin/busybox ash BusyBox v1.22.1 (Debian 1:1.22.0-19+b3) built-in shell (ash) Enter 'help' for a list of built-in commands. ~ $
さてこれで配列が使えるかというと
~ $ array=(a b c d) ; for i in ${array[@]} ; do echo $i ;done sh: syntax error: unexpected "("
だめです。
bashに戻して配列を使うワンライナーを流す。配列をa b c d として、一個ずつ取り出してechoするだけのワンライナー。
~ $ /bin/bash tmin@tminserver:~$ array=(a b c d) ; for i in ${array[@]} ; do echo $i ;done a b c d
これが想定の動きです。
dashならどうか?どうやらdashはashがベースらしいですが。
tmin@tminserver:~$ /bin/dash $ array=(a b c d) ; for i in ${array[@]} ; do echo $i ;done /bin/dash: 1: Syntax error: "(" unexpected
だめですね。
つまり#!/bin/sh ではなく#!/bin/bash って書いとけばシェルなら大丈夫。ワンライナーなら確実にbashに切り替えてから実行すればおk。
でもDebianと違って、ESXiのデフォルトではbashがないのですよ!!
■解決法
1.配列+forによる繰り返しをやめてwhileでなんとかする。
何も根本的解決になってないですけど。コマンド結果を配列に入れて、一個ずつ取り出して代入してうんたらかんたら、っていうくらいのことならwhile使えばいいよ。
※問題点
複数の配列を使いたかった処理の場合これでは目的を達成できない。
達成しようとした場合、一撃で終わらせたかった処理が2撃以上になるか、シェルが冗長になるかどちらか。
とはいえ、今回自分がハマっていた処理についてはこれで行けた。(lsの結果を配列に突っ込んでうんたらかんたら、みたいなのだったと記憶している)
2.set -- $arrayを使う
https://stackoverflow.com/questions/26091758/arrays-in-shell-script-not-bashstackoverflow.com
さすがStack Over Flow。
$ array="0 1 2 3" ; set -- $array ; for i in $array ; do echo $i ;done 0 1 2 3 ~ $
でも複数配列うまく使うのはまた難しそう。
私的シェル芸入門
シェル芸的なものを書くことが多くなってきたのでいろいろまとめてみます。
■シェル芸とは
シェル芸の定義バージョン1.1
マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力一撃で終わらすこと。あるいはそのときのコマンド入力のこと。
https://b.ueda.tech/?page=01434シェル芸 | 上田ブログ
※以降の記述はあくまで個人的なシェル芸的なものにたいするあれこれなので、厳密にはシェル芸の定義や考え方に反する記述が含まれているかもしれません。
厳密なシェル芸?を学びたい方はなんか他で確認してください。
■シェル芸のための考え方(個人的)
・一個ずつポチポチやってく繰り返し作業がめんどい→それシェルでできない?
ex)1~100までのhostname,日付、連番のファイルを作成
$ for i in {1..100} ;do touch $(hostname)_$(date +_%Y%m%d)_$i.txt ;done
みたいな
・Teratermのブロードキャストで一斉に時刻入りのディレクトリ作りたい。
けどあとでログ収集サーバとかに送るからホスト名とかで区別つけときたい
$ mkdir $(hostname)_$(date +_%Y%m%d%H%M)
・とあるディレクトリの中の一定時間経過したファイルを探して削除したい
$ find . -type f -mmin +10 | xargs rm -fv
■よく使うコマンドつなぎ方
・$(command) ()内のコマンドを展開する。上記例の中で$(hostname)は$ hostnameの結果をその場所に入れる。
$ echo $(hostname) tmin_Arch $ hostname tmin_Arch
ファイル名に入れたいときとか、grepの検索ワードに使いたいときとかに重宝する。他にも多々。
基本的な動きは`command`と同じ。
・; 前のコマンド終了したら次のコマンドを実行する。
$ hostname; date tmin_Arch 2018年 8月 15日 水曜日 23:15:39 JST
ちなみにワンライナーでforとかwhileとかの繰り返しを使うときは、シェルで使うときの改行の代わりにこれを使ったりする。
$ for i in {1..100} ;do touch $(hostname)_$(date +_%Y%m%d)_$i.txt ;done
シェル的には
for i in {1..100} ; do touch $(hostname)_$(date +_%Y%m%d)_$i.txt done
みたいな感じ。
・| 前のコマンドの結果を後ろに渡す。cat した結果を後ろに渡してgrepしてそれを後ろに渡してawkで整形したり、
findで探したファイルを| xargsで後ろに渡して後ろのコマンドの引数にしたりする。
■よく使うコマンド(※他にも多々あり)
cat ファイルの中身を見る。というか、ファイルの中身を標準出力に表示する。
grep 指定の検索文字に引っかかった行を表示する。ちなみに-Eで正規表現でor検索とか、-vで除外とか色々できる。
fgrepとかegrep使えよって話もあるけどめんどいのであれ。
find 指定条件のファイル(or ディレクトリ)を探す。名前で絞ったり、時間で絞ったり色々できる。
xargs パイプと組み合わせて使うと、前のコマンド結果が複数のときにうまいこと後ろのコマンドの引数として渡してくれる。
awk 出力の整形、特にnカラム目を抜き出すのによく使う。
sed 出力の整形、特にある文字列を置き換えたり削除したりするのによく使う。
echo 文字列を標準出力に表示する。だけのはずだが変数食わせたりコマンド結果食わせたりすることもできる。
ls 指定ディレクトリの中身の一覧を出す。ls | grep hoge* とかしたのをxargsで渡してううんたらかんたら
tail 最後のn行の出力
head 最初のn行の出力
■マスターすると?
※メリット
・シェルっぽいことをシェル作らなくてもできる。のでシェルを残しにくい環境で一撃でなにかの処理を終わらせることができる。
・シェルっぽいことをしぇる作らなくてもできる。のでシェルスクリプト規約とかめんどくせーっていう環境で「これはコマンドです!!」と言い張ることができるかもしれない。
・ループ処理とか使うと同じようなコマンドを何十回と打って、みたいなケアレスミスが起きやすい状況を回避できるかもしれない。
・このコマンドの結果をここに代入して・・・の繰り返しみたいな手順を簡略化できるかもしれない。
・楽しい。なんかかっこいいかもしれない。
・達成感が得られるかもしれない。
※デメリット
・再現性に乏しいため、エビデンス残してないともっかい作るのがめんどい。
・複雑になってくると自分であとから読み返して解読に時間がかかるときが稀によくある。
・長くなってきたときにちゃんと検証しないと途中でなにか間違ってたときに想定外の動きをする可能性が増える。
やってみよー1
の
$ curl http://blog.ueda.asia/?page_id=1434 2> /dev/null | grep -A 1 h2 | tail -n 1 | sed 's;;;g' | tr -d '[:space:]' | awk '{print}' マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力一撃で終わらすこと。
がcurl先変わっててうまく動かないので、書き直してみたよ。
awkで整形したの
$ curl https://b.ueda.tech/?page=01434 2> /dev/null | grep -A 1 'id="1.1"' | tail -n 1 | awk -F '>' '{print $2}' | awk -F '<' '{print $1}' マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力一撃で終わらすこと。あるいはそのときのコマンド入力のこと。
sedで整形したの
$ curl https://b.ueda.tech/?page=01434 2> /dev/null | grep -A 1 'id="1.1"' | tail -n 1 | sed -e 's/<p>//' -e 's/<.p>//' マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力一撃で終わらすこと。あるいはそのときのコマンド入力のこと。
curlしたのをh2でgrepするともう一つ引っかかるものがあるため、id="1.1"でgrepして-A 1で後ろ一行。
$ curl https://b.ueda.tech/?page=01434 2> /dev/null | grep -A 1 'id="1.1"' <h2 id="1.1">シェル芸の定義バージョン1.1</h2> <p>マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力一撃で終わらすこと。あるいはそのときのコマンド入力のこと。</p>
tail -n 1 で定義の方
タグで囲まれた部分を抽出。
$ curl https://b.ueda.tech/?page=01434 2> /dev/null | grep -A 1 'id="1.1"' | tail -n 1 | <p>マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力一撃で終わらすこと。あるいはそのときのコマンド入力のこと。</p>
あとは<p></p>をなんとか削除する。
awk版はawkで区切り文字を>指定して2カラム目を抜き出す&区切り文字<を指定して1カラム目を抜き出す。
sed版はsedで単純に<p>と</p>を一個ずつ削除。
正規表現自由自在マンならもうちょいエレガントにかけるかも。
やってみよー2
ディスクのパーティション情報一括取得ワンライナー
/dev/sd*で認識されてるsd*を抽出→while read LINEで出力をLINEに格納してループ開始。
→echoで$LINEを出力 ; partedにわたしてMB単位で情報出力。
# ls /dev/sd* | grep -v [0-9] | while read LINE ; do echo "******$LINE******" ; parted $LINE unit mib print ;done ******/dev/sda****** モデル: ATA WDC WDS120G1G0A- (scsi) ディスク /dev/sda: 114473MiB セクタサイズ (論理/物理): 512B/512B パーティションテーブル: gpt ディスクフラグ: 番号 開始 終了 サイズ ファイルシステム 名前 フラグ 1 1.00MiB 451MiB 450MiB ntfs Basic data partition hidden, diag 2 451MiB 551MiB 100MiB fat32 EFI system partition boot, esp 3 551MiB 567MiB 16.0MiB Microsoft reserved partition msftres 4 567MiB 113970MiB 113403MiB ntfs Basic data partition msftdata 5 113971MiB 114472MiB 501MiB ntfs hidden, diag ******/dev/sdb****** モデル: ATA WDC WD10EARS-00Y (scsi) ディスク /dev/sdb: 953870MiB セクタサイズ (論理/物理): 512B/512B パーティションテーブル: msdos ディスクフラグ: 番号 開始 終了 サイズ タイプ ファイルシステム フラグ 1 1.00MiB 100001MiB 100000MiB primary ext4 boot 2 100001MiB 103801MiB 3800MiB primary linux-swap(v1) 3 103801MiB 900993MiB 797192MiB primary ext4 4 900993MiB 953869MiB 52876MiB extended 5 900994MiB 953803MiB 52809MiB logical ext4 ******/dev/sdc****** モデル: ATA Hitachi HDS72101 (scsi) ディスク /dev/sdc: 953870MiB セクタサイズ (論理/物理): 512B/512B パーティションテーブル: msdos ディスクフラグ: 番号 開始 終了 サイズ タイプ ファイルシステム フラグ 1 0.03MiB 100006MiB 100006MiB primary ntfs boot 2 100006MiB 953869MiB 853863MiB primary ntfs ******/dev/sdd****** モデル: ATA ST3500320AS (scsi) ディスク /dev/sdd: 476939MiB セクタサイズ (論理/物理): 512B/512B パーティションテーブル: gpt ディスクフラグ: 番号 開始 終了 サイズ ファイルシステム 名前 フラグ 1 1.00MiB 513MiB 512MiB fat32 EFI System boot, esp 2 513MiB 8705MiB 8192MiB linux-swap(v1) Linux swap 3 8705MiB 59905MiB 51200MiB btrfs Linux filesystem 4 59905MiB 476939MiB 417034MiB btrfs Linux filesystem
それではみなさん、よいbash lifeを!!
コンシューマーキーが凍結されたらしいmikutterでTwitterを使うために
■発端
GW最終日ですが、mikutter立ち上げたらエラーが出てました。
■衝撃(?)の事実
コンシューマーキーが凍結されていたらしい。
作者のtoshi_a氏はすでにtwitter界から追放されているため、自分でなんとかする必要があります。
幸い上記記事内に今回のような事態を想定して、回避するためのTipsを用意してくれています。toshi_a氏神かよ。
■回避
とのこと。やってみましょうか。
僕は割と一般人なので普通に開発者ページの使い方がよくわかってませんでしたが。
参考にしたのはこの辺
下記リンクからTwitterアカウントでログインして
https://dev.twitter.com/user/login
「Authorize app」 をクリックして続行。
アプリケーションページ(https://dev.twitter.com/apps)から「Create a new application」
アプリ名「mikutter」はすでに使用されているので、適当に入れます。
ついでにアプリの説明の部分も適当に。
サイトはサイト持ってない人はtwitter.comとかで良いようです。
成功するとこんな感じでキーが取得できます。