ShellScript » 履歴 » リビジョン 2
リビジョン 1 (kanata, 2025/04/13 15:54) → リビジョン 2/3 (kanata, 2025/04/13 15:54)
# ShellScript
目指せシェル芸人
{{rawhtml(<canvas id="map"></canvas><script src="/javascripts/pagemap.min.js"></script><script>pagemap(document.querySelector("#map"));</script>)}}
{{toc}}
主にbashについて書きます
# Basic
## if文
```
if [ 条件1 ]
then
処理1
elif [ 条件2 ]
then
処理2
else
処理3
fi
```
### 数値比較
|数値評価演算子 | 意味 |
|----------------|----------------------------------------|
|数値1 -eq 数値2 | 数値1と数値2が等しい場合に真 |
|数値1 -ne 数値2 | 数値1と数値2が等しくない場合に真 |
|数値1 -gt 数値2 | 数値1が数値2より大きい場合に真 |
|数値1 -lt 数値2 | 数値1が数値2より小さい場合に真 |
|数値1 -ge 数値2 | 数値1が数値2より大きいか等しい場合に真 |
|数値1 -le 数値2 | 数値1が数値2より小さいか等しい場合に真 |
こういう書き方
```
if [ "1" -eq "0" ]
```
<br><br><br><br><br>
### 文字列比較
|文字列評価演算子 | 意味 |
|-------------------|---------------------------------|
|文字列 | 文字列の長さが0より大きければ真 |
|-n 文字列 | 文字列の長さが0より大きければ真 |
|! 文字列 | 文字列の長さが0であれば真 |
|-z 文字列 | 文字列の長さが0であれば真 |
|文字列1 = 文字列2 | 2つの文字列が等しければ真 |
|文字列1 != 文字列2 | 2つの文字列が等しくなければ真 |
こういう書き方
```
if [ "ABC" = "abc" ]
if [ -z "${FILEPATH}" ]
if test -z "${FILEPATH}"
```
### ファイルチェック
|ファイルチェック演算子 | 意味 |
|------------------------|--------------------------------------|
|-d ファイル名 | ディレクトリなら真 |
|-f ファイル名 | 通常ファイルなら真 |
|-L ファイル名 | シンボリックリンクなら真 |
|-r ファイル名 | 読み取り可能ファイルなら真 |
|-w ファイル名 | 書き込み可能ファイルなら真 |
|-x ファイル名 | 実行可能ファイルなら真 |
|-s ファイル名 | サイズが0より大きければ真 |
|ファイル1 -nt ファイル2 | ファイル1がファイル2より新しければ真 |
|ファイル1 -ot ファイル2 | ファイル1がファイル2より古ければ真 |
こういう書き方
```
if [ -f "${FILEPATH}" ]
if test -f "${FILEPATH}"
if [ "${FILE1}" -nt "${FILE2}" ]
```
### 論理結合
|論理結合演算子 | 意味 |
|---------------|----------------------------------------|
|! 条件 | 条件が偽であれば真 |
|条件1 -a 条件2 | 条件1が真、かつ、条件2が真であれば真 |
|条件1 -o 条件2 | 条件1が真、または、条件2が真であれば真 |
こういう書き方
```
# AND(論理積演算子)
if [ -f a.txt -a -f b.txt ]
if [ "$a" -eq "$b" ] && [ "$c" -eq "$d" ]
# OR(論理和演算子)
if [ -f a.txt -o -f b.txt ]
if [ "$a" -eq "$b" or "$c" -eq "$d" ]
if [ "$a" -eq "$b" ] || [ "$c" -eq "$d" ]
# NOT(否定論理演算子)
if [ ! "$a" -eq "$b" ]
```
### 変数に任意の文字が含まれているかどうか
```
if echo $WORD | grep "任意の文字" >/dev/null
then
echo "文字含まれている"
else
echo "文字含まれていない"
fi
```
### bashのワイルドカード
実はif文内でワイルドカードの判定ができる。
```bash
STRING="abc"
if [[ $TEST == *"b"* ]]
echo "matched"
else
echo "unmatched"
fi
```
ワイルドカード部分 * は、ダブルコーテーションで囲んだりすると動かなくなるので注意。
### bashの正規表現
そもそもgrepを使わないでも、変数に任意の文字が含まれているかどうか判定できる。
```bash
STRING="abc"
if [[ "$STRING" =~ ^ab[cd]$ ]]; then
echo "matched"
else
echo "unmatched"
fi
```
正規表現部分 \^ab[cd]$ は、ダブルコーテーションで囲んだりすると動かなくなるので注意。
## 展開
bash入門
https://rat.cis.k.hosei.ac.jp/article/linux/bash_intro.html
### ブレース展開
カンマ区切りの文字列をブレースで囲んだものはブレース展開されます。
```
$ echo {a,b,c}
a b c
```
ブレースの前後に文字列があるとそれを反映した展開がなされます。
```
$ echo 0{a,b,c}1
0a1 0b1 0c1
```
ブレース展開はまとめてファイルやディレクトリを作成するときなどによく使われます。
```
$ find . -type d
.
$ mkdir -p a/b{0,1}/c{0,1}
$ find . -type d
.
./a
./a/b0
./a/b0/c0
./a/b0/c1
./a/b1
./a/b1/c0
./a/b1/c1
```
### チルダ展開
チルダはユーザーのホームディレクトリに展開されます。
```
$ whoami
user1
$ echo ~
/home/user1
```
### ファイル名展開
ファイル名展開はグロブ(glob)とも呼ばれます。
| パターン | 効果 |
|----------|---------------------------------------------------------------------------------------------------------|
| * | 0文字以上の任意の文字列にマッチ。 |
| ? | 1文字の任意の文字列にマッチ。 |
| [...] | ブラケットで挟まれている文字のうち任意の1文字にマッチ。正規表現におけるブラケットの解釈とほぼ同じです。 |
```
$ ls
a adc.txt aec.txt b
$ echo *
a adc.txt aec.txt b
$ echo ?
a b
$ echo a[abcd]*
adc.txt
# マッチするものが無かった場合パターンはそのままの形で残ります
$ echo xyz*
xyz*
```
### 変数展開
変数を参照する場合に文字列を加工することができる。
| パターン | 効果 |
|-------------------------|--------------------------------------------------------------------------------------|
| ${変数名#word} | 変数から値を取り出し、その先頭部分がwordと一致した場合その部分を取り除きます。(先頭から前方最短一致した位置まで) |
| ${変数名##word} | 変数から値を取り出し、その先頭部分がwordと一致した場合その部分を取り除きます。(先頭から前方最長一致した位置まで) |
| ${変数名%word} | 変数から値を取り出し、その後方部分がwordと一致した場合その部分を取り除きます。(末尾から後方最短一致した位置まで) |
| ${変数名%%word} | 変数から値を取り出し、その後方部分がwordと一致した場合その部分を取り除きます。(末尾から後方最長一致した位置まで) |
| ${変数名/pattern/word} | 変数から値を取り出し、最初にpatternとマッチする部分だけをwordで置換します。 |
| ${変数名/#pattern/word} | 変数から値を取り出し、最初にpatternとマッチする部分だけをwordで置換します。(上と同じ)|
| ${変数名/%pattern/word} | 変数から値を取り出し、最後にpatternとマッチする部分だけをwordで置換します。 |
| ${変数名//pattern/word} | 変数から値を取り出し、patternとマッチする全ての部分をwordで置換します。 |
| ${変数名:=word} | 変数が NULL の場合 word に置換され、かつ変数に代入される。 |
| ${変数名:-word} | 変数が NULL の場合 word を出力する。 |
| ${変数名:+word} | 変数が NULL 以外の場合 word に置換され、かつ変数に代入される。 |
| ${変数名:?word} | 変数が NULL の場合、標準エラー出力にwordを表示し、現在実行中のスクリプトを中止する。 |
| ${#変数名} | 変数に設定されている文字数を返す。 |
| ${変数名:offset} | 変数に設定されているoffset文字+1から出力する。offsetを負の値にすると語尾から数える。 |
| ${変数名:offset:length} | 変数に設定されているoffset文字+1からlength分出力する。 |
| ${変数名,} | 先頭だけ小文字に変換 |
| ${変数名,,} | 全部小文字に変換 |
| ${変数名\^} | 先頭だけ大文字に変換 |
| ${変数名\^\^} | 全部大文字に変換 |
### 算術式展開
expr や、bcコマンドを使用しなくても、$((計算式))で計算ができる。
```
$ echo $(( 1 + 2 + 3 ))
6
```
Qiita - Bash $((算術式)) のすべて
https://qiita.com/akinomyoga/items/2dd3f341cf15dd9c330b
### コマンド置換
コマンド置換には以下の 2 つの形式があります。
```
$(command)
`command`
```
今までは一般的にバッククォート `command` が使われてきたが、$(command) も使えます。
$(command)は、入れ子ができるので、$(command)の方を使ったほうが良い。
~~~
$ TEST=`date`;echo $TEST
2016年 8月 28日 日曜日 18:04:33 JST
$ TEST=$(date);echo $TEST
2016年 8月 28日 日曜日 18:04:41 JST
~~~
次のコマンドはカレントディレクトリの中で「abc」を含むすべてのファイルの先頭10行を表示します。
```
$ head -10 $(grep -l abc *)
```
### ヒアドキュメント
こんな感じ。ftpぐらいなら自動化できる。
```bash
$ command << token
text
token
```
例
```bash
#!/bin/bash
# Script to retrieve a file via FTP
FTP_SERVER=ftp.nl.debian.org
FTP_PATH=/debian/dists/lenny/main/installer-i386/current/images/cdrom
REMOTE_FILE=debian-cd_info.tar.gz
ftp -n << _EOF_
open $FTP_SERVER
user anonymous me@linuxbox
cd $FTP_PATH
hash
get $REMOTE_FILE
bye
_EOF_
```
### ヒアストリング
変数を擬似的にファイルのように扱える。
```
$ cat < hoge
-bash: hoge: そのようなファイルやディレクトリはありません
cat <<< hoge
hoge
$ HOGE=hogefuga
$ cat < $HOGE
-bash: hogefuga: そのようなファイルやディレクトリはありません
$ cat <<< $HOGE
hogefuga
```
## 特殊なシェル変数
| 変数 | 内容 |
|------|------------------------------------------------------|
| $0 | 実行したスクリプトの文字列 |
| $1, $2, ..., $9 | 第1引数, 第2引数, ..., 第9引数 |
| $# | 与えられた引数の数 |
| $* | 与えられたすべての引数. 引数全体が"で囲まれている |
| $@ | 与えられたすべての引数. 引数一つ一つが"で囲まれている|
| $- | シェルに与えられたフラグ |
| $? | 最後に行ったコマンドの戻り値 |
| $$ | 現在のシェルのプロセス番号 |
| $! | 最後にバックグラウンドで行ったコマンドのプロセス番号 |
| $_ | 実行シェルの文字列 |
| IFS | 区切り文字のリスト |
## シェル変数と環境変数
| | 特徴 | 確認 | 設定 | 削除 |
|------------|----------------------------------------|------|-----------------------------------|------|
| シェル変数 | シェル変数は子プロセスに引き継がれない | set | 変数名=値 | unset # シェル変数、環境変数両方消去される
| 環境変数 | シェル変数は子プロセスに引き継がれる | env | export 変数名 or export 変数名=値 |unset # シェル変数、環境変数両方消去される
>ちなみに、環境変数を定義するには export コマンド以外にも declare コマンドに -x オプションを付けて実行する方法もある。
## プロセス置換
以下の文法に従えば、コマンドの実行結果を一時ファイルに吐いておく必要がなくなる。
~~~
<(command)
~~~
例
~~~
$ sdiff <(date;sleep 1;date) <(date;date)
2016年 8月 28日 日曜日 18:00:15 JST 2016年 8月 28日 日曜日 18:00:15 JST
2016年 8月 28日 日曜日 18:00:16 JST | 2016年 8月 28日 日曜日 18:00:15 JST
~~~
## 連想配列
連想配列が使える。
>ver4系から使える機能です。Macのbashはver3系のため、brew等でアップデートしましょう。
```
$ declare -A hashTable
$ hashTable["hoge"]="fuga"
$ echo ${hashTable["hoge"]}
fuga
```
# bash開発機能
## 文法チェック
```
$ bash -n test.sh
```
## 実行内容をトレース
```
$ bash -x test.sh
```
## ステップ実行
擬似シグナルDEBUGを使う。擬似シグナルDEBUGはシェルが文を実行するたびに発行される。
```
#!/bin/bash
trap 'read -p "next(LINE:$LINENO)>> $BASH_COMMAND"' DEBUG
```
## 未定義の変数をエラーとして扱う
```
#!/bin/bash
set -u
# ${TEST}が未定義ならエラーになる
echo ${TEST}
```
## 終了ステータスが0以外ならその時点で終了
```
#!/bin/bash
set -e
#ls -yでエラーになり、後続の処理は実行されない
ls -y
ls -l
```
# Sample
## ファイルを一行ずつ読み込んで処理する
```
#!/bin/sh
# ファイルを1行ずつ読み込んで表示
TESTFILE=./hoge.txt
while read LINE
do
echo $LINE
done < $TESTFILE
```
処理単位も行にしたい時は、冒頭に以下を書く
```
IFS='
'
```
## 区切り文字(デミリタ)で区切られた文字列の任意のフィールドを取得する
cut方式とawk方式がある
```
echo "abc:def:ghi:jkl"|cut -d: -f3
echo "abc:def:ghi:jkl"|awk -F':' '{print $3}'
```
ダブルクォーテーションで囲まれている場合
```
$ grep 設定項目名 設定ファイル | sed 's/[^"]*"\([^"]*\)"[^"]*/\1/g'
```
シングルクォーテーションで囲まれている場合
```
$ grep 設定項目名 設定ファイル | sed "s/[^']*'\([^']*\)'[^']*/\1/g"
```
イコールで定義している場合
```
$ grep 検索する設定項目 設定ファイルPATH | sed 's/ //g' | awk -F'=' '{ print $2}'
```
## 標準入力とファイルの内容を結合
~~~
$ cat test
efg
$ echo -n abcd |cat - test
abcdefg
~~~
>大抵のコマンドには - (標準入力の内容をファイル相当として扱う)を実装している
## サーバと連続でやりとりするシェルスクリプト
以下は、サーバからもらった計算式を計算して、送り返している例
```
#!/bin/sh
exec 5<>/dev/tcp/[host]/[port]
for I in {1..101}
do
cat 0<&5>test.txt &
sleep 1
pkill cat
WORD=`cat test.txt|tail -1|sed 's/=//g'`
ANSWER=`ecgo ${WORD}|bc`
echo ${ANSWER} > &5
echo Debug [${I}] ${WORD} '=' ${ANSWER}
done
exit 0
```
## 文字列⇔16進数表現⇔2進数表現
文字列→16進数表現
```
echo "ABC"|xxd
```
文字列→2進数表現
```
echo "ABC"|xxd -b
```
16進数表現→文字列
```
echo 4142434445464748 | xxd -ps -r
```
16進数表現→2進数表現
```
echo 4142434445 | xxd -ps -r|xxd -b
```
2進数表現→16進数表現
```
echo "obase=16; ibase=2; 010000010100001001000011010001000100010100001010" | bc
```
2進数表現→文字列
```
echo "obase=16; ibase=2; 010000010100001001000011010001000100010100001010" | bc|xxd -ps -r
```
----
bcコマンドなくても、このぐらいはできる。
2進数表現→10進数表現
```
echo $((2#101010))
echo "obase=10; ibase=2; 101010" | bc
```
8進数表現→10進数表現
```
echo $((04567))
echo "obase=10; ibase=8; 4567" | bc
```
16進数表現→10進数表現
```
echo $((0xABCD))
echo "obase=10; ibase=16; ABCD" | bc
```
## 指定した相対PATHからの、絶対パス・ディレクトリ名・ファイル名の取得
```
$ cd /tmp/trash
$ readlink -f test.txt
/tmp/trash/test.txt
$ dirname `readlink -f test.txt`
/tmp/trash
$ basename `readlink -f test.txt`
test.txt
```
## IPアドレスの取得
いろんな方法があるが、NICのインタフェース名が不定だし、複数NICが刺さっていたりもするので、これという方法が無い。
```
$ ifconfig eth1 | grep -w 'inet' | cut -d: -f2 | awk '{ print $1}'
$ ip addr list venet0 | grep "inet " | cut -d' ' -f6 | cut -d/ -f1
$ ip a s | grep "inet " | cut -d' ' -f6 | cut -d/ -f1
$ hostname -i
$ hostname -I
```
実は[こういう技](https://ex1.m-yabe.com/archives/4638)がある
素晴らしい
```
$ curl ifconfig.io
```
## IPアドレスのソート
```
$ cat ip_list.txt | sort -n -t'.' -k1,1 -k2,2 -k3,3 -k4,4
```
## 特定のファイルのバックアップファイルを作成する
```
$ find PATH -type f -name 'ファイル名' | xargs -n 1 -I{} cp {} {}.bk
```
## 特定のファイルをバックアップディレクトリにコピーする
```
$ find PATH -type f -name 'ファイル名' | xargs -n 1 -I{} cp {} バックアップ先PATH
```
## 特定のファイルに対してのみgrepを行う
```
$ find PATH -type f -name 'ファイル名' | xargs -n 1 -I{} grep '検索キーワード' {}
```
## 指定したファイルの合計サイズを取得する
```
$ du -bhc [ファイルPATH等条件] | tail -n 1
```
または
```
$ ls -la [ファイルPATH等条件] | awk '{ total += $5 }; END { print total }'
```
## 対話型コマンドを自動実行する
対話中に判断や計算が入るとexpectでやるしかない。
```
#!/bin/expect -f
spawn ./121-calculation
set i 0
while {$i <= 100000} {
puts "\n#### $i ######"
expect "*="
set quest $expect_out(buffer)
set answer [exec echo $quest | tail -1 | tr -d '=' | tr -d '\r' | bc]
send "$answer\n"
incr i 1
}
```
命令解説
* spawn コマンド実行
* puts 画面表示(実行したコマンドには影響しない)
* expect コマンド出力する文字にマッチングするまでここで止まる
* set 変数 定数 変数設定
* set 変数 $expect_out(buffer) コマンド実行結果を変数に(ただし、直前のではなく、全部入るっぽい)
* set 変数 [外部コマンド] 外部コマンドの実行結果を変数に(最新行を選択するtail -1 と 変な改行コード入ってエラーになるので tr -d '\r' を入れるのがコツ)
* send "変数名\n" コマンドに実行結果を送る(データ終端を\nにしているコマンドが多いので、明示的に\nを書く)
* incr 変数名 定数 加算
## どういう形式で圧縮されているか判別して展開する
環境合わせて、適当に修正が必要
```
#!/bin/sh
LIST=`ls|grep -v flatting.sh`
for WORD in ${LIST}
do
FILE_TYPE=`file ${WORD}`
if echo ${FILE_TYPE}|grep "Zip archive data" >/dev/null
then
unzip ${WORD}
rm ${WORD}
elif echo ${FILE_TYPE}|grep "shell archive text" >/dev/null
then
sh ${WORD}
rm ${WORD}
elif echo ${FILE_TYPE}|grep "bzip2 compressed data" >/dev/null
then
bunzip2 -c ${WORD} > ${WORD}_out
rm ${WORD}
elif echo ${FILE_TYPE}|grep "POSIX tar archive" >/dev/null
then
tar xvf ${WORD}
rm ${WORD}
elif echo ${FILE_TYPE}|grep "gzip compressed data" >/dev/null
then
gunzip -c ${WORD} > ${WORD}_out
rm ${WORD}
elif echo ${FILE_TYPE}|grep "xz compressed data" >/dev/null
then
xz -d -c ${WORD} > ${WORD}_out
rm ${WORD}
elif echo ${FILE_TYPE}|grep "ASCII cpio archive" >/dev/null
then
cpio -idv < ${WORD}
rm ${WORD}
elif echo ${FILE_TYPE}|grep "ASCII text" >/dev/null
then
if cat ${WORD} |grep "This is dummy file" >/dev/null
then
rm ${WORD}
else
base64 -d ${WORD} > ${WORD}_out
if [ ${?} = 0 ]
then
rm ${WORD}
fi
fi
else
:
fi
done
exit 0
```
## 一定時間ごとに任意のコマンドを実行する
もちろんwatchコマンドを使ってもいいが、以下の方法でも可能。
```
$ yes '[任意のコマンド]; sleep [任意のインターバル秒数];' | sh
```
## 文字列への色つけ
```
txtund=$(tput sgr 0 1) # Underline
txtbld=$(tput bold) # Bold
txtred=$(tput setaf 1) # Red
txtgrn=$(tput setaf 2) # Green
txtylw=$(tput setaf 3) # Yellow
txtblu=$(tput setaf 4) # Blue
txtpur=$(tput setaf 5) # Purple
txtcyn=$(tput setaf 6) # Cyan
txtwht=$(tput setaf 7) # White
txtrst=$(tput sgr0) # Text reset
echo "${txtbld}This is bold text output from shell script${txtrst}"
```
## プログレスバーの表示
```
show_progress() {
echo -ne '##### (33%)\r'
sleep 1
echo -ne '############# (66%)\r'
sleep 1
echo -ne '####################### (100%)\r'
echo -ne '\n'
}
```
## スピナーの表示
```
show_spin () {
rotations=3
delay=0.1
for i in `seq 0 $rotations`; do
for char in '|' '/' '-' '\'; do
echo -n $char
sleep $delay
printf "\b"
done
done
}
```
## あらゆるファイルの解凍
```
function extract {
if [ -z "$1" ]; then
# display usage if no parameters given
echo "Usage: extract <path/file_name>.<zip|rar|bz2|gz|tar|tbz2|tgz|Z|7z|xz|ex|tar.bz2|tar.gz|tar.xz>"
else
if [ -f $1 ] ; then
# NAME=${1%.*}
# mkdir $NAME && cd $NAME
case $1 in
*.tar.bz2) tar xvjf ../$1 ;;
*.tar.gz) tar xvzf ../$1 ;;
*.tar.xz) tar xvJf ../$1 ;;
*.lzma) unlzma ../$1 ;;
*.bz2) bunzip2 ../$1 ;;
*.rar) unrar x -ad ../$1 ;;
*.gz) gunzip ../$1 ;;
*.tar) tar xvf ../$1 ;;
*.tbz2) tar xvjf ../$1 ;;
*.tgz) tar xvzf ../$1 ;;
*.zip) unzip ../$1 ;;
*.Z) uncompress ../$1 ;;
*.7z) 7z x ../$1 ;;
*.xz) unxz ../$1 ;;
*.exe) cabextract ../$1 ;;
*) echo "extract: '$1' - unknown archive method" ;;
esac
else
echo "$1 - file does not exist"
fi
fi
}
```
> このコマンドも有用と思われ
俺的備忘録 〜なんかいろいろ〜 - ファイルの圧縮方式に合わせて自動的に解凍してくれる『dtrx』コマンド
https://orebibou.com/2016/07/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E5%9C%A7%E7%B8%AE%E6%96%B9%E5%BC%8F%E3%81%AB%E5%90%88%E3%82%8F%E3%81%9B%E3%81%A6%E8%87%AA%E5%8B%95%E7%9A%84%E3%81%AB%E8%A7%A3%E5%87%8D%E3%81%97%E3%81%A6/
## POSIX仕様の中でif文中でパターンマッチングする
```
if [ -n "$X" -a -z "${X%%pattern}" ] ; then echo "true"; else echo "false"; fi
```
# KnowHow
シェルスクリプトを極める
http://www.slideshare.net/bsdhack/ss-43064758
## バッファリングさせない
stdoutがttyじゃないとbufferingするコマンドは多い
stdbufで解決できる
```
$ command | stdbuf -oL command | command
```
>行単位でバッファを吐き出す
```
$ command | stdbuf -oO command | command
```
>バッファリングさせない
以下が神解説です
俺的備忘録 〜なんかいろいろ〜 - sedやawk、grepをバッファさせずに動作させる
https://orebibou.com/2017/06/sed%e3%82%84awk%e3%80%81grep%e3%82%92%e3%83%90%e3%83%83%e3%83%95%e3%82%a1%e3%81%95%e3%81%9b%e3%81%9a%e3%81%ab%e5%8b%95%e4%bd%9c%e3%81%95%e3%81%9b%e3%82%8b/
## ファイルディスクリプタ
* 標準入力は 0
* 標準出力は1
* 標準工ラー出力は2
シエルではリダイレクト記号の前に数字
```
&> ファイル # 標準出力・標準エラー出力を共にリダイレクト
3> ファイル # fd3をファイルにリダイレクト
4< ファイル # fd4をファイルからリダイレクト
5<&- # fd5をクローズ
6>&7 # fd7をfd6に複製(fd6をfd7にリダイレクト)
```
---
実装
```
command > file
```
>結果、fdが標準出力として使用される
```
command > file 2>&1
```
>結果、fdが標準出力、および、標準エラー出力として使用される
```
commandA |commandB
```
>結果、commandA の標準出力が commandB の標準入力になる
```
$ ./echo "test" 3>&1 1>&2 2>&3
```
>標準エラー出力と標準出力を入れ替える
## プロセスの出力同士をdiffで比較
```
$ diff <(commandA)<(commandB)
$ commandA|(commandB|diff /dev/fd/3 -) 3<&0
```
> 3<&0 で標準入力(fd0) を fd3 に複製
> commandA の出力が fd3 に出力される
> /dev/fd/3 からの入力が commandA の出力
## パイプの途中のプロセスの終了コード
```
$ exec 3>&1
ret1=`{ { commandA 1; echo $? 1>&3; } | commandB 2; } 3>&1`
ret2=$?
```
> コマンド 1 の $? を 1>&3 で fd3 に出力
> fd3 の内容を 3>&1 で ret1 に格納
## ループ処理でのファイルディスクリプタの活用
```
exec 3<&0 0<<EOF
`command`
EOF
while read line
do
:
done
exec 0<&3 3<&-
```
>exec 3<&0 で fd0 を fd3 に複製
>exec 0<<EOF でヒアドキュメントを fd0 として使用
>exec 0<&3 で複製した fd0 を復帰
>exec 3<&- で fd3 をクローズ
>パイプを使わないのでプロセスが生成されない
## evalを用いた配列的な変数アクセス
```
# $1: 配列名
# $2: インデックス
# $3-: 値
setarray()
{
_index_="__`echo $1 $2 | sed -e 's/ //g'`"
shift 2
eval $_index_'='"$@"
}
getarray()
{
eval echo '${'__`echo $* | sed -e 's/ //g'`'}'
}
```
## ファイルの上書き
元ファイルを書き替える
```
$ コマンド < ファイル > ファイル # 絶対ダメ!
$ (rm ファイル ; コマンド > ファイル ) < ファイル # ファイルの中身が消えない方法
② ③ ①
```
>① でファイルが読み込みモードでオープンされる。
>② でオープンされたファイルが削除される。
>③ で新しい(別な)ファイルが書き込みモードでオープンされる。
①のファイルと③のファイルは同じファイル名ですが、 inode が異なっているため、別のファイルとして扱われる。
1<>を使う方法もある
```
cat srcfile 1<> srcfile
```
## シェルスクリプトでの排他処理
```
lockfile="/var/tmp/`basename ${0}`"
test -f ${lockfile} || touch ${lockfile}
```
>test(1) と touch(1) の間に他のプロセスの test(1) が実行されると 排他処理が失敗する
```
lockfile="/var/tmp/`basename ${0}`"
if ln -s $$ ${lockfile} 2> /dev/null
then
: # メイン処理を実行
else
echo "${0}: exist another instance" 1>&2
exit 255
fi
trap 'rm -f ${lockfile}; exit' 0 1 2 3 11 15
```
>シンボリックリンクの作成は atomic
>PID をリンクする事でロックしたプロセスが特定できる
## 英単語の先頭文字を大文字に変換する
```
$ echo "foo" | awk '{ print toupper(substr($0, 1, 1)) substr($0, 2, length($0) - 1) }'
```
>awk 組み込みの substr() 、 toupper() を使用する
```
$ eval eval `echo "foo" | sed 's/(.)(.*)//bin/echo -n 1 | tr "[a-z]" "[A-Z]"; echo 2/g'`
```
>sed 単体では実現できないのでコマンド列を出力して eval
```
$ echo foo bar buz|sed 's/\b\([a-z]\)/\U\1/g'
```
>実はsed単体でできました
## IF文のような分岐処理をワンライナーで行わせる
```
$ コマンド && TRUE || FALSE
```
「&&」以降はコマンドがTRUEの場合に、「||」以降はFALSEの場合に実行されるコマンドとなる。
例
```
$ cat test.log
abc [31/Oct/2015:20:36:51 +0900] ghi [20]
jkl [31/Oct/2015:20:36:55 +0900] pqr [0]
stu [31/Oct/2015:20:37:00 +0900] yz [40]
000 [31/Oct/2015:20:37:05 +0900] ghi [5]
$ grep abc test.log >/dev/null && echo 1 || echo 2
1
$ grep azc test.log >/dev/null && echo 1 || echo 2
2
```
## exの活用
ファイルの行操作に ex を活用する
- 直接行の追加や削除が可能(一時ファイル不要)
– 実は vi なので正規表現など強力な編集操作が可能
– echo やヒアドキュメントで編集コマンドを指定可能
### exで行の追加
```
$ /bin/ex -s ファイル << EOF
行番号 a
コンテンツ
コンテンツ
:
.
w!
EOF
```
>行番号で指定した行の下にコンテンツを挿入する
> . で挿入モードを終了し w! で内容をファイルに出力する
### exで行削除処理
```
$ /bin/ex -s ファイル << EOF
行番号 d
w!
EOF
```
>行番号で指定した行を削除する
> w! で内容をファイルに出力する
### exで行番号を指定した行置換処理
```
$ /bin/ex -s ファイル << EOF
行番号 s/パターン/置換文字列/
w!
EOF
```
>行番号で指定した行のパターンを置換文字列に置換する
> w! で内容をファイルに出力する
### exでパターンを指定した行置換処理
```
$ /bin/ex -s ファイル << EOF
/パター /s/ 置換文字 /
w!
EOF
```
>最初に発見したパターンを置換文字列に置換する
>s の後ろの連続した // は直前の正規表現(パターン)を示す
> w! で内容をファイルに出力する
### exでファイルから指定された行を削除する
```
$ cat basefile
first line
second line
third line
:
:
$ cat target
2
3
5
:
:
```
>元のファイル (basefile) には複数の行が含まれている
>削除する行は別なファイル (target) に格納されている
>target ファイルには行番号が 1 行ずつ格納されている
パターンを指定した行置換処理
```
$ sort target | awk '{ printf "%ddn", $1-(NR-1); } END { print "w!" }' | ex -s basefile
```
>削除済みの行を考慮する必要がある
>>→ 2 行目を削除すると今までの 3 行目が 2 行目になる
>ex で行を削除する
>awk で削除した行を考慮した行番号に対する行削除を出力
## シェルスクリプトで標準入力を受け取る
3パターンある。
### cat - で 標準入力を受け取り、利用できる。
```
#!/bin/sh
if [ -p /dev/stdin ] ; then
a=$(cat -)
echo "input string is ${a}"
else
echo "nothing stdin"
fi
```
### readを使うと、標準入力を読むことができる
```
#!/bin/bash
while read i ; do
#数字を読み込んで1足して出力する。
echo $((i+1))
done
```
他の方法に比べて高コストになりがち
### /dev/stdinを使う
```
#!/bin/bash
awk '{print $1+1}' < /dev/stdin
```
## 一時ファイルを作らずにコマンドの出力結果を利用する(Command Substitution)
<(コマンド)記法、ksh、bash、zsh限定。
コマンドに対する入出力を、ファイルのように指定することが出来る。
* コマンドの標準出力を入力ファイルとして <(コマンド)
* コマンドの標準入力を出力ファイルとして >(コマンド)
例えば、
```
$ diff <(ls) <(ls -a)
```
これと同義
```
$ ls > a
$ ls -a > b
$ diff a b
```
## パイプラインに任意の値を入れ込む
### 文字列入れ
```
$ seq 2 | (echo 'Header'; cat; echo 'Footer')
Header
1
2
Footer
```
```
$ seq 5 | (echo 'obase=2'; cat) | bc
1
10
11
100
101
```
### ファイル入れ
"-"が標準入力のこと
```
$ seq 11 12 | cat data -
1
2
11
12
```
## パイプで渡したコマンドの終了ステータスを得る
```
$ cat test.txt |cat|cat|cat|cat|cat
$ echo ${PIPESTATUS[@]}
0 0 0 0 0 0
```
## bashでパイプで受け取った値を自動エスケープして出力する
俺的備忘録 〜なんかいろいろ〜 - bashでパイプで受け取った値を自動エスケープして出力する
https://orebibou.com/2017/07/bash%e3%81%a7%e3%83%91%e3%82%a4%e3%83%97%e3%81%a7%e5%8f%97%e3%81%91%e5%8f%96%e3%81%a3%e3%81%9f%e5%80%a4%e3%82%92%e8%87%aa%e5%8b%95%e3%82%a8%e3%82%b9%e3%82%b1%e3%83%bc%e3%83%97%e3%81%97%e3%81%a6/
## echo、printfでパイプから受けた値を出力する
俺的備忘録 〜なんかいろいろ〜 - echo、printfでパイプから受けた値を出力する
https://orebibou.com/2017/07/echo%e3%80%81printf%e3%81%a7%e3%83%91%e3%82%a4%e3%83%97%e3%81%8b%e3%82%89%e5%8f%97%e3%81%91%e3%81%9f%e5%80%a4%e3%82%92%e5%87%ba%e5%8a%9b%e3%81%99%e3%82%8b/
## サロゲートペアやUnicode結合文字を考慮して文字単位に分解する
NG
```
$ echo そのチェㇷ゚は𩸽🇯🇵 | grep -o .|tr '\n' '/'
そ/の/チ/ェ/ㇷ/゚/は/𩸽/🇯/🇵
```
OK
```
$ ruby -e 'puts "そのチェㇷ゚は𩸽🇯🇵".scan(/\X/).join("/")'
そ/の/チ/ェ/ㇷ゚/は/𩸽/🇯🇵
```
# awk KnowHow
AWK 一行野郎百裂拳 - 日本 GNU AWK ユーザー会 - No-ip.org
http://gauc.no-ip.org/awk-users-jp/material/100_one_liners_20131223.pdf
> 神
## 特定の N 行を表示する
```
$ awk 'NR==N'
```
## 空行を削除する
```
$ awk 'NF'
$ awk '$0'
$ awk '/./'
```
## 文字数をカウントする(wc -c)
```
$ awk '{n+=length($0)} END{print n}'
```
## 単語数をカウントする(wc -w)
```
$ awk '{n+=NF} END{print n}'
```
## 行数をカウントする(wc -l)
```
$ awk 'END{print NR}'
```
## 行末の空白やタブを削除する
```
$ awk '{sub(/[ \t]+$/, "")}1'
```
## Unix の改行コードに変換する
```
$ awk 'sub(/\r$/,"")'
```
## Windows の改行コードに変換する
```
$ awk 'sub(/$/,"\r")'
```
## 逆順出力をする(tac)
```
$ awk '{a[i++]=$0} END{for(j=i-1; j>=0;) print a[j--]}'
```
## 重複するレコードを削除する(uniq)
```
$ awk '!a[$0]++'
```
## ソートしないで重複行を削除する
```
awk '!a[$0]++' FILE
```
## 行番号を付ける(nl)
```
$ awk '$0 = NR OFS $0'
```
## 標準出力にそのまま出力する(cat -)
```
$ awk '1'
```
## 正規表現にマッチした行を表示する(grep)
```
$ awk '/hogehoge/'
```
## 正規表現にマッチしない行を表示する(grep -v)
```
$ awk '! /hogehoge/'
```
## コメント行を削除する
```
$ awk '! /^#/'
```
## C言語のように複数行にまたがったコメント行を削除する
/* と */ に囲まれた行の場合
```
$ cat file | awk '/\/\*/, /\*\//{next}{print}'
```
## 指定行から指定行までを表示する
```
$ awk 'NR==10,NR==20'
```
## 偶数行を表示する
```
$ awk 'NR%2==0'
```
## 奇数行を表示する
```
$ awk 'NR%2'
```
## 特定のフィールド数を持つ行のみを抜き出す
```
$ cat file
りんご 100円 192個
ばなな 170円 210個
爽健美茶 150円
グラタン ソース マカロニ チーズ じゃがいも
$ cat file | awk 'NF>=2 && NF<=3'
りんご 100円 192個
ばなな 170円 210個
爽健美茶 150円
```
## awkで\[\]\(カギカッコ\)内の値に応じて行を抽出する
```
$ awk -F '[][]' '$4 >= 〇〇' ログファイルPATH
```
例
```
$ cat log.txt
abc [31/Oct/2015:20:36:51 +0900] ghi [20]
jkl [31/Oct/2015:20:36:55 +0900] pqr [0]
stu [31/Oct/2015:20:37:00 +0900] yz [40]
000 [31/Oct/2015:20:37:05 +0900] ghi [5]
$ awk -F '[][]' '$4 >= 20' log.txt
abc [31/Oct/2015:20:36:51 +0900] ghi [20]
stu [31/Oct/2015:20:37:00 +0900] yz [40]
```
## 異なるデミリタを指定して、要素を取り出す
```
$ cat log.txt
abc [31/Oct/2015:20:36:51 +0900] ghi [20]
jkl [31/Oct/2015:20:36:55 +0900] pqr [0]
stu [31/Oct/2015:20:37:00 +0900] yz [40]
000 [31/Oct/2015:20:37:05 +0900] ghi [5]
$ awk -F '[][]' '{print $1}' log.txt
abc
jkl
stu
000
$ awk -F '[][]' '{print $2}' log.txt
31/Oct/2015:20:36:51 +0900
31/Oct/2015:20:36:55 +0900
31/Oct/2015:20:37:00 +0900
31/Oct/2015:20:37:05 +0900
$ awk -F '[][]' '{print $3}' log.txt
ghi
pqr
yz
ghi
$ awk -F '[][]' '{print $4}' log.txt
20
0
40
5
```
## awkの編集結果をファイルにリダイレクトで出力して保存する
awkでリダイレクトを行う場合、たとえば「tail -F」などと組み合わせて利用する場合、単純に「>」で指定してもリダイレクトが行われない場合がある。
* 例:tail -F で「/work/test」というファイルを常時監視し、その内容に日付を付け足して「/work/test_log」ファイルに出力しようとしている。
```
$ tail -F /work/test | awk '{ print strftime("%Y/%m/%d %H:%M:%S") " " $0 }' > /work/test_log
```
tail -Fとawkを組み合わせてファイルに出力する場合、以下のようにリダイレクトの前に「{ system (" ")}」と記述する必要がある。
```
$ tail -F /work/test | awk '{ print strftime("%Y/%m/%d %H:%M:%S") " " $0 } { system (" ") }' > /work/test_log
```
## Excelのフィルタのように、ファイルから〇〇以上、〇〇以下で行を抽出する
```
$ awk '列 >= 条件 && 列 <= 条件' 対象ファイル
$ awk '列 == 条件' 対象ファイル
$ awk '列 ~ /条件/ 対象ファイル'
```
```
$ cat test.txt
1 abc 10
2 def 15
3 ghi 0
$ awk '$3 >=5 && $3 <=10' test.txt
1 abc 10
$ awk '$2=="def"' test.txt
2 def 15
$ awk '$2 ~ /abc|ghi/' test.txt
1 abc 10
3 ghi 0
```
## ◯◯時からXX時までの間のログを抽出
```
$ awk -F - '"Apr 5 14:30:00" < $1 && $1 <= "Apr 5 15:00:00"' /var/log/messages
```
## grepのように行を抽出
```
$ awk '/文字列/' 対象ファイル
```
## 任意の行を抽出
```
$ awk 'NR==行' 対象ファイル
$ awk 'NR==最初の行,NR==終わりの行' 対象ファイル
```
## 文字列で範囲を指定して出力
```
$ awk '/任意の開始文字列/,/任意の終了文字列/' 対象ファイル
```
例えば、ファイル2カラム目の「00:00:02」~「00:00:04」までを抽出する場合
```
$ awk '$2 >= "00:00:02" && "00:00:04" >= $2' /tmp/sample.log
```
複数条件(1行目、文字列XXを含む行を抽出とか)で抽出する場合は、awk内でifを使用できる。
```
$ awk '{if(条件1||条件2) print}' 対象ファイル # or条件
$ awk '{if(条件1&&条件2) print}' 対象ファイル # and条件
```
# sed KnowHow
## 基本的な使い方
```
$ sed 's/置換前文字列/置換後文字列/g' ファイルPATH
$ コマンド | sed 's/置換前文字列/置換後文字列/g'
```
## ◯行目~◯行目にある特定の文字列を置換する場合
```
$ sed '4,8s/line/gyou/g' test.txt
```
## 特定の文字列を含む行のみ置換する場合
```
$ sed '/検索文字列/s/置換前文字列/置換後文字列/g'
```
## その行に表示される◯番目の文字列を置換する
```
$ sed 's/置換前文字列/置換後文字列/何文字目か'
```
## マッチした文字列を再利用する
1個だけと複数指定可能な2つの方法がある
### 1個だけの方法
置換後文字列の所に&を書くと、その部分がマッチした文字列に置き換わる。
~~~
$ echo "abc def hij klm" |sed -e 's/a[a-z]c/XXX & XXX/g'
XXX abc XXX def hij klm
~~~
### 複数指定可能な方法
\\( と \\) で囲んだものがマッチした場合、順番に \1 \2 \3 と指定できる。
~~~
$ echo "abc def hij klm" |sed -e 's/^\([a-z][a-z][a-z]\) \(d[a-z][a-z]\) \([a-z][a-z][a-z]\) \([a-z][a-z][a-z]\)/XXX \4 \3 \2 \1 XXX/g'
XXX klm hij def abc XXX
~~~
## 指定した行番号の箇所に行を挿入する
```
$ sed '◯i 挿入する行'
$ sed '4i testline' test.txt
```
## 指定した行の後ろに行を挿入する
```
$ sed '◯a 挿入する行'
$ sed '4a testline' test.txt
```
## 指定した複数の各行の前、後ろに行を挿入する
```
$ sed '◯,◯i 挿入する行' #前に挿入する場合
$ sed '◯,◯a 挿入する行' #後に挿入する場合
```
## 指定したキーワードを持つ行の前・後に行を挿入する
```
$ sed '/キーワード/i 挿入する行' #前に挿入する場合
$ sed '/キーワード/a 挿入する行' #後に挿入する場
```
## ◯行目~◯行目を削除する
```
$ sed '◯,◯d'
$ sed '1,4d' test.txt
```
## 決まったキーワードを持つ行を除外する
```
$ sed '/キーワード/d'
$ sed '/line2/d' test.txt
```
## ◯行目の内容を上書きする
```
$ sed '◯c 置き換え後の行'
$ sed '4c testline' test.txt
```
## 特定のキーワードを持つ行を上書きする
```
$ sed '/キーワード/c 置き換え後の行'
$ sed '/line3/c testline' test.txt
```
## ファイルの内容を上書きする
```
$ sed -i '置換条件' ファイルPATH
$ sed 's/line 1/line #/g' test.txt
```
## 複数の置換条件を適用する
```
$ sed -e '置換条件' -e '置換条件' ...
$ sed -e 's/line/gyou/g' -e '5c aaaaaaa' test.txt
```
## ファイルに書いた置換条件を読み込む
```
$ sed -f スクリプトファイルPATH
$ cat /root/sed_script
s/line/gyou/g
5c aaaaaaa
$ sed -f ./sed_script test.txt
```
## 小文字→大文字の変換をする
```
$ sed 's/\(.*\)/\U\1/'
$ sed 's/\(.*\)/\U\1/' test.txt
```
## 大文字→小文字の変換をする
```
$ sed 's/\(.*\)/\L\1/'
$ sed 's/\(.*\)/\L\1/' test.txt
```
## ダブルコーテーション、シングルコーテーションに囲まれた文字列を抽出する
~~~
$ sed 's/^.*"\(.*\)".*$/\1/' # ダブルクォーテーションの場合
$ sed "s/^.*'\(.*\)'.*$/\1/" # シングルクォーテーションの場合
~~~
ちな、grepやawkでもできる
~~~
$ grep -Po '(?<=")[^",]+(?=")' # ダブルクォーテーション
$ grep -Po "(?<=')[^',]+(?=')" # シングルクォーテーション
$ awk -F'['\''"]' '{print $2}'
~~~
## ダブルクォーテーションで囲まれた文字列に対して処理を行う
```
$ sed 's/"\([^"]*\)"/"置換後の値"/' 対象のファイルPATH
$ sed 's/"\([^"]*\)"/"replace"/' test.txt
```
## シングルクォーテーションで囲まれた文字列に対して処理を行う
```
$ sed 's/"\([^"]*\)"/"置換後の値"/' 対象のファイルPATH
$ sed "s/'\([^']*\)'/'replace'/" test.txt
```
## メールアドレスを『○○○@●●●●●』というようにマスキング置換する
```
$ sed "s/[^@ ]*@[^@]*\.[^@ ]*/○○○@●●●●●/g" ファイルPATH
```
## sedで行頭の置換を指定する
```
$ sed 's/^置換前文字列/置換後文字列/g' ファイルPATH
$ sed 's/^test/aaaa/g' test.txt
```
## sedで行頭以外の置換を指定する
```
$ sed 's/\([^^]\)置換前文字列/置換後文字列/g' ファイルPATH
$ sed 's/\([^^]\)test/aaaa/g' test.txt
```
## 指定したディレクトリ内のファイルを再帰的に置換する
```
$ sed 's/置換前/置換後/g' $(find 対象のフォルダPATH -type f)
```
## 特定の文字列~文字列間を置換する
```
$ sed '/文字列(開始)/,/文字列(終了)/s/○○○/●●●/g' 対象のファイルPATH
$ sed '/line2/,/line3/s/test/aaaa/g' test.txt # line2とline3という文字の間にあるtestをaaaaに置換
$ sed '/line2/,/line3/caaaa' test.txt # line2がある行とline3がある行、及びその間の行をaaaaに入れ替え
```
## 日本語(マルチバイト文字)のみを置換する
```
$ LANG=C sed 's/[\x80-\xFF]//g' ファイルPATH
$ sed 's/[\x80-\xFF]/●/g' test.txt
```
# Other Command KnowHow
## grep
### or検索
```
$ grep -e 検索したい文字列1 -e 検索したい文字列2 検索したいテキストファイル
```
### 大文字・小文字を区別しない
```
$ grep -i 検索したい文字列 検索したいテキストファイル
```
### 検索にヒットする前後の行を出力
検索にヒットした前の行を出力する場合
```
$ grep 検索したい文字列 -B 出力したい行数 検索したいテキストファイル
```
検索にヒットした後の行を出力する場合
```
$ grep 検索したい文字列 -A 出力したい行数 検索したいテキストファイル
```
前後の行を同時に出力したい場合は、Cオプションを付与する。
```
$ grep 検索したい文字列 -C 出力したい行数 検索したいテキストファイル
```
### 検索にヒットする行数を取得する
```
$ grep -c 検索したい文字列 検索したいテキストファイル
```
### ファイルの行番号を出力する
```
$ grep -n 検索したい文字列 検索したいテキストファイル
```
### 単語で検索する
例えばadで検索するとaddressやmadといった、別の意味を持つ単語も引っかかってしまうので
```
$ grep -w 検索したい文字列 検索したいファイル
```
### 複数のキーワードをパターン化したファイルを読込み、合致した行を出力する
```
$ grep -f 検索パターンを記述したファイル 検索したいファイル
```
### 検索キーワードを持つファイルのリストを取得する
```
$ grep -l 検索パターンを記述したファイル 検索したいフォルダ/*
```
## find
ここ読んでよく勉強すること!!1
俺的備忘録 〜なんかいろいろ〜 - findコマンドで覚えておきたい使い方12個
http://orebibou.com/2015/03/find%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%A7%E8%A6%9A%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84%E4%BD%BF%E3%81%84%E6%96%B912%E5%80%8B/
## date
### 今日を基準として、任意の時間経過された日時を求める
```
$ date
2015年 10月 31日 土曜日 21:29:13 JST
$ date -d '-1day'
2015年 10月 30日 金曜日 21:29:05 JST
$ date -d '1day'
2015年 11月 1日 日曜日 21:29:08 JST
$ date -d '1hour'
2015年 10月 31日 土曜日 22:31:33 JST
$ date -d '1month'
2015年 12月 1日 火曜日 21:31:38 JST
$ date -d '1year'
2016年 10月 31日 月曜日 21:31:42 JST
```
### 来月の月初、3ヶ月先の月末日付を求める
```
$ date +%Y/%m/%d
2015/10/31
$ date +%Y/%m/01
2015/10/01
$ date +%Y/%m/01 -d '+1 month'
2015/12/01
$ date +%Y/%m/%d -d "-1day `date +%Y%m01 -d '+4 month'`"
2016/02/29
```
### 閏年の判断
閏年じゃない場合はエラーになる。
```
$ date -d 2012-02-29 +%Y
2012
$ date -d 2013-02-29 +%Y
date: `2013-02-29' は無効な日付です
```
-f-とすれば標準入力から複数の日付を受け付けてくれる。
こうすれば閏年だけ抜き出せる。
```
$ seq 1900 2400 | sed 's/$/-02-29/' | date -f- +%Y 2> /dev/null
```
## sort
### バイト数(KB、MB、GB etc…)を並び替えする
```
$ du -h /home | sort -hr
```
## uniq
### 重複した行・重複していない行だけを表示させる
重複している行のみを出力させる場合は「-d」を、重複のない(ユニークな)行をのみを出力させる場合は「-u」オプションを付与する。
```
$ cat test.txt | uniq -d
$ cat test.txt | uniq -u
```
## watch
### 実行コマンドがエラーになった場合、watchコマンドを終了する
```
$ watch -e 実行コマンド
```
なお、「-e」の場合はコマンドの実行は停止するが何かキーを押下するまで画面はそのままとなる。
### 実行コマンドに変化があった場合、watchコマンドを終了する
```
$ watch -g 実行コマンド
```
## xargs
### 基本的な使い方
以下のようにパイプでつなぐことで前のコマンド(コマンド1)で取得した値(標準出力)を利用してxargsで指定した別のコマンド(コマンド2)に引数として渡して実行させる事ができるコマンド
```
$ コマンド1 | xargs コマンド2
```
### 実行されるコマンド内容を表示させる
```
$ コマンド1 | xargs -t コマンド2
```
### コマンドライン一行に引数をいくつ渡すか指定する
```
$ コマンド1 | xargs -n 引数の数 コマンド2
```
### 引数の値を明示的に利用する
```
$ コマンド1 | xargs -I{} コマンド2 {}
```
例えば「/work」フォルダ配下にあるファイルに対し、元のファイル名の後ろに「.bk」という文字列を付け足してコピーする場合
```
$ find /work -type f | xargs -t -I{} cp {} {}.bk
```
### コマンドの実行をするかどうか確認する
```
$ コマンド1 | xargs -p コマンド2
```
### 複数プロセスを同時に実行させる
```
$ コマンド1 | xargs -P 最大プロセス数 コマンド2
```
### 引数の区切り文字を指定する
```
$ コマンド1 | xargs -d区切り文字 コマンド2
```
## eval
### 変数の2重展開
```
#!/bin/sh
# 変数 VARに値が入力されている
VAR="test"
# 変数 EVAL_VARに、変数名である「VAR」という文字列を入力
EVAL_VAR="VAR"
# 変数 EVAL_VARを呼び出す
# echo $EVAL_VAR
eval echo '$'$EVAL_VAL
```
### 変数が配列の場合の2重展開
```
#!/bin/sh
# 変数「VAL」を配列として値を代入していく
VAL[0]="line 1"
VAL[1]="line 2"
VAL[2]="line 3"
# 変数 EVAL_VALに、変数名である「VAL」という文字列を入力
EVAL_VAL="VAL"
# 変数 EVAL_VALを呼び出す
eval echo '$'$EVAL_VAL
# 変数 EVAL_VALを配列として全行呼び出す
eval echo '${'$EVAL_VAL'[@]}'
# 変数 EVAL_VALを配列として[1]を呼び出す
eval echo '${'$EVAL_VAL'[1]}'
```
## ssh
### ローカルのbashの設定使ってssh接続
```
$ ssh -t user@host 'bash --rcfile <( echo ' $(cat ~/.bashrc ~/.bash_function_etc... | base64 ) ' | base64 -d)'
```
>環境によってはbase64に-w0オプションが必要かも?
### Dockerにローカルのbashrcやvimrcを使って接続
```
$ docker run --rm -it コンテナ bash -c '/bin/bash --rcfile <(echo -e '$(cat ~/.bashrc ~/.bash_function_etc... |base64)'|base64 -d)'
```
今のセッションの環境変数をそのまま使う場合は
```
$ docker run --rm -it コンテナ bash -c '/bin/bash --rcfile <(echo -e '$(cat <(set) <(alias)|base64 -w0)'|base64 -d)'
```
### ローカルにあるスクリプトを配布せずにリモート先で実行させる
```
$ ssh リモート先のユーザ名@リモート先のホスト名(IPアドレス) 'sh ' < 実行させたいスクリプトのパス
$
```
### 直接リモート先のマシン上でコマンドを用いたsedを行わせる
```
$ sed ユーザ名@リモートホスト sed s/aaaaa/`hostname`/g 対象ファイル # ローカル側でhostnameが実行される
$ sed ユーザ名@リモートホスト 'sed s/aaaaa/`hostname`/g' 対象ファイル # リモート側でhostnameが実行される
```
### リモート側のファイルにローカルファイルの内容を追記する方法
```
$ ssh ユーザ名@リモートホスト "cat >> /追記するリモートファイルのパス" < /追記させるローカルファイルのパス
$ ssh test@192.168.0.240 "cat >> /tmp/test_remote.text" < test_local.txt
```
ローカル側のコマンドの実行結果(ローカル側のファイルに対し、sedを実行した後の内容を追記するなど)を追記する場合は、以下のようにする。
```
$ ローカル側で標準出力を行うコマンド | ssh ユーザ名@リモートホスト "cat >> /追記するリモートファイルのパス"
$ sed 's/test/AAAA/g' text_local.txt | ssh test@192.168.0.240 "
```
### ssh経由でディレクトリにあるファイル一覧をdiffする
公開鍵認証じゃないとダメかもしれない
```
$ diff <(ssh ユーザ名@ホスト名 'find /確認するPATH -type f | sort') <(find /確認するPATH -type f | sort)
$ diff <(ssh test@192.168.0.240 find /work -type f | sort) <(find /work -type f | sort)
```
rsyncコマンドで代用できる
```
$ rsync --checksum --dry-run -rvce "ssh -p ポート番号" ユーザ名@ホスト名:/対象ディレクトリ /対象ディレクトリ
```
### ssh経由でファイル内容をdiffする
```
$ diff <(ssh ユーザ名@ホスト名 'cd /確認するPATH; grep -Rn "" ./ | sort') <(cd /確認するPATH; grep -Rn "" ./ | sort)
$ diff <(ssh test@192.168.0.240 'cd /work;grep -Rn "" ./ | sort') <(cd /work;grep -Rn "" ./ | sort)
```
### ローカルでファイル・フォルダを圧縮し、リモートでアーカイブファイルとして保持させる
```
$ tar zcvf - /アーカイブ化したいディレクトリのパス | ssh ユーザ名@リモートホスト "cat > /リモート側で作成するアーカイブ・ファイルのパス
$ tar zcvf - /wort_test | ssh test@192.168.0.240 "cat > /wort_test.tar.gz"
```
### ローカルのアーカイブファイルをリモートで解凍する
```
$ ssh ユーザ名@リモートホスト "tar zxvf -C リモート側で展開させたいフォルダ -" < /リモート側で解凍させたいアーカイブファイルのパス
$ ssh test@192.168.0.240 "tar zxvf - -C /test1234" < test.tar.gz
```
### リモートでファイル・フォルダを圧縮し、ローカルでアーカイブファイルとして保持させる
```
$ ssh ユーザ名@リモートホスト "tar -cf - /アーカイブ化したいディレクトリのパス" | gzip > /ローカル側で作成するアーカイブ・ファイルのパス
$ ssh test@192.168.0.240 "tar -cf - /work/test" | gzip > /root/test.tar.gz
```
### リモートのアーカイブファイルをローカルで解凍する
```
$ ssh ユーザ名@リモートホスト "cat /ローカル側で解凍させたいアーカイブファイル" | tar zxvf - -C ローカル側で展開させたいフォルダ
$ ssh test@192.168.0.240 "cat /work.tar.gz" | tar zxvf - -C /test/
```
### 踏み台サーバ経由でログインを行う
```
$ ssh ユーザ名@接続先のホスト名(IPアドレス) -o 'ProxyCommand ssh 踏み台サーバのユーザ@踏み台サーバのホスト名 nc %h %p'
$ ssh test1@192.168.0.2-o 'ProxyCommand ssh test2@192.168.0.3 nc %h %p'
```
### ssh接続時に直接コマンドを実行する
```
$ ssh ユーザ名@接続先のホスト名(IPアドレス) '接続先で実行させるコマンド'
```
### sshでsuによるユーザ切り替え時に自動でパスワード入力をさせる
```
$ ssh login@host "su - user -c 'command' << EOF
password
EOF"
```
## dd
### バイナリファイルでのcutコマンド相当
```
$ cat file | dd bs=1 skip=100
```
### バイナリファイルでのheadコマンド相当
固定長レコード限定
```
$ dd bs=[レコード長] count=[抜き出したい行数] skip=[読み飛ばしたい行数] if=[入力ファイル] of=[出力ファイル]
```
# シェル芸
UPS友の会
https://www.usptomo.com/
上田ブログ
https://blog.ueda.asia/
シェル芸人達
https://daichkr.hatelabo.jp/collection/960679194075891200
日々是迷歩
http://papiro.hatenablog.jp/
slideshare シェル芸
http://www.slideshare.net/search/slideshow?searchfrom=header&q=%E3%82%B7%E3%82%A7%E3%83%AB%E8%8A%B8&ud=&ft=&lang=&sort=
## Open usp Tukubai
基本的に、以下からダウンロードして展開すれば使える
https://uec.usp-lab.com/TUKUBAI/CGI/TUKUBAI.CGI?POMPA=DOWNLOAD
コマンドの説明は以下
https://uec.usp-lab.com/TUKUBAI_MAN/CGI/TUKUBAI_MAN.CGI?POMPA=LIST
Github - ShellShoccar-jpn/Parsrs
https://github.com/ShellShoccar-jpn/Parsrs
## egzact
シェルの弱点を補おう!"まさに"なCLIツール、egzact
http://qiita.com/greymd/items/3515869d9ed2a1a61a49 https://github.com/greymd/egzact
## エクシェル芸
俺的備忘録 〜なんかいろいろ〜 - Excelファイル(~.xls/~.xlsx)をLinuxコンソール上でCSV方式に変換する方法
https://orebibou.com/2016/12/excel%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab%ef%bd%9e-xls%ef%bd%9e-xlsx%e3%82%92linux%e3%82%b3%e3%83%b3%e3%82%bd%e3%83%bc%e3%83%ab%e4%b8%8a%e3%81%a7csv%e6%96%b9%e5%bc%8f%e3%81%ab%e5%a4%89%e6%8f%9b/
## ワンライナー
### アクセスログから、接続元IPをカウントし、合計で100回以上アクセスしているIPのみアクセスが多い順に表示する
```
cat /var/log/httpd/access_log* | awk '/\"GET .*\" 200 / || /\"CONNECT .*\" 200 / {cnt[$1]+=1};END{for (key in cnt) {if(cnt[key] > 100) {print cnt[key],key}}}' | sort -nr
```
### アクセスログから、リファラなしの閲覧先ドメインをカウントし、合計で100回以上アクセスがあるURLのみアクセスが多い順に表示する
```
cat /var/log/httpd/access_log* | awk -F\" '/GET .* 200 / || /CONNECT .* 200 / {cnt[$2]+=1};END{for (key in cnt) {if(cnt[key] > 100) {print cnt[key],key}}}' | sed -e 's% HTTP/...%%g' | sort -nr
```
# シェル芸化
シェル芸の定義はこうだ。
>マウスも使わず、ソースコードも残さず、GUI ツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理を CLI 端末へのコマンド入力一撃で終わらすこと。
では、やってみましょう。
全ての手続きを1行に。
## 1行化の基本
```
$ echo "a"
$ echo "b"
$ echo "c"
```
;で区切ることで1行化できる。
```
$ echo "a";echo "b";echo "c"
```
### && の利用
```
$ hoge ; date
-bash: hoge: コマンドが見つかりません
2016年 6月 19日 日曜日 11:01:32 JST
```
&&で区切ると、hogeが正常終了した場合のみ、後続のコマンドを実行する。
正常終了とは、コマンドの復帰値($?)が0の場合を指す。
```
$ hoge && date
-bash: hoge: コマンドが見つかりません
```
これは以下と等価である。
```
#!/bin/bash
hoge
RET=$?
if [ $RET -eq "0" ]
then
date
else
exit $RET
fi
```
### || の利用
```
$ hoge ; date
-bash: hoge: コマンドが見つかりません
2016年 6月 19日 日曜日 11:01:32 JST
```
||で区切ると、hogeが異常終了した場合のみ、後続のコマンドを実行する。
異常終了とは、コマンドの復帰値($?)が0以外の場合を指す。
```
$ hoge || date
-bash: hoge: コマンドが見つかりません
2016年 6月 19日 日曜日 11:17:16 JST
```
これは以下と等価である。
```
#!/bin/bash
hoge
RET=$?
if [ $RET -nq "0" ]
then
date
fi
```
## if文の1行化
これが
```
RET="0"
if [ $RET -eq "0" ]
then
echo "true"
else
echo "false"
fi
```
こうじゃ!
```
RET="0";if [ $RET -eq "0" ]; then echo "true"; else echo "false"; fi
```
RETの値を変えれば、分岐されていることを確認できる。
### && , || の利用
以下のように、&& や || を利用することもできます。
```
$ RET="0";[ $RET -eq "1" ] || echo "It work"
It work
$ RET="0";[ $RET -eq "0" ] && echo "It work"
It work
```
## for文の1行化
これが
```
for I in {1..10}
do
echo $I
done
```
こうじゃ!
```
for I in {1..10} ; do echo $I ; done
```
これでもいけるらしい
~~~
for I in {1..5};{ echo $I;}
for((;;)){ echo 1;sleep 1; } # 無限ループ
~~~
## while文の1行化
これが
```
I=1
while [ $I -le 10 ]
do
echo $I
I=$((I+1))
done
```
こうじゃ!
```
I=1; while [ $I -le 10 ]; do echo $I;I=$((I+1)); done
```
## case文の1行化
これが
```
case $I in
a ) echo "a1"
echo "a2";;
b ) echo "b1"
echo "b2";;
* ) echo "default";;
esac
```
こうじゃ!
```
case $I in a ) echo "a1"; echo "a2";; b ) echo "b1"; echo "b2";; * ) echo "default";; esac
```
## 関数の1行化
これが
```
funcSample(){
echo "It work"
}
funcSample
```
こうじゃ!
```
funcSample(){ echo "It work"; };funcSample
```
## シェル芸ブブ化
シェルスクリプトをシェル芸化(ワンライナー)するスクリプト
[[シェル芸ブブ化]]
# Test/Lint
POSTD - Bashアプリケーションをテストする
http://postd.cc/bash%e3%82%a2%e3%83%97%e3%83%aa%e3%82%b1%e3%83%bc%e3%82%b7%e3%83%a7%e3%83%b3%e3%82%92%e3%83%86%e3%82%b9%e3%83%88%e3%81%99%e3%82%8b/
ShellCheck - ShellCheck, a static analysis tool for shell scripts
http://www.shellcheck.net/ https://github.com/koalaman/shellcheck
Batsを使ったシェルスクリプトのテスト
https://rcmdnk.com/blog/2019/10/11/computer-github-bash/
pre-commitでShellCheckを使う
https://rcmdnk.com/blog/2023/01/09/computer-shell-python/
AWK commands equivalent to SQL query – Data manipulation
https://subhadip.ca/unixlinux/awk-commands-equivalent-to-sql-query-data-manipulation/4/
>SQLのINNER JOINやLEFT OUTER JOINをawkで実現
シェルスクリプトにxUnitを使ってみる
https://qiita.com/filunK/items/aa067383aaa317594d17
# Memo
## sed
Qiita - sedのパターンスペース・ホールドスペースの動作を図で学ぶ
http://qiita.com/gin_135/items/773fec1343a69c9f90d6
俺的備忘録 〜なんかいろいろ〜 - sedのデバッグやHTML化ができる『sedsed』
https://orebibou.com/2016/11/sed%E3%81%AE%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E3%82%84html%E5%8C%96%E3%81%8C%E3%81%A7%E3%81%8D%E3%82%8B%E3%80%8Esedsed%E3%80%8F/
## Misc
Defensive BASH Programming
http://www.kfirlavi.com/blog/2012/11/14/defensive-bash-programming
Perl1行野郎
http://web.archive.org/web/20020203071153/www13.cds.ne.jp/~ohsaru/perl/oneline.html
WICKED COOL SHELL SCRIPTS
http://www.intuitive.com/wicked/wicked-cool-shell-script-library.shtml
Bourne Shell自習テキスト
http://lagendra.s.kanazawa-u.ac.jp/ogurisu/manuals/sh-text/sh/
Qiita - richmikan@github
http://qiita.com/richmikan@github
glot.io - 様々な言語のコードスニペットが共有できるサイト
https://glot.io/
CMD-CHALLENGE - なんかCTFのようなシェル芸勉強サイト!!
https://cmdchallenge.com/
Thanks Driven Life - shellcheck を Docker で実行する
http://gongo.hatenablog.com/entry/2017/03/28/223757
Command Challenge - ★CTFみたい!!★
https://cmdchallenge.com/
explainshell.com - 難しめのシェル芸の解析
https://explainshell.com/
Yakst - 私が他人のシェルスクリプトから学んだこと
https://yakst.com/ja/posts/31
Qiita - bashの組込みコマンド自作によるスクリプトの高速化
https://qiita.com/satoru_takeuchi/items/7d424fa5ef1e33ace4df
Qiita - ネットワーク越しでパイプしたり、あらゆるデバイス間でデータ転送したい!
https://qiita.com/nwtgck/items/78309fc529da7776cba0
ble - Bashを動的にシンタックスハイライトする
https://github.com/akinomyoga/ble.sh
if 文と test コマンド | UNIX & Linux コマンド・シェルスクリプト リファレンス
https://shellscript.sunone.me/if_and_test.html
bashスクリプティング研修の資料を公開します
https://www.m3tech.blog/entry/2018/08/21/bash-scripting
set -eのもとで特定のコマンドの終了ステータスを変数に入れるシェルスクリプトのスニペット
https://gfx.hatenablog.com/entry/2021/12/15/153937
bash スクリプトの実行中上書き動作について
https://zenn.dev/mattn/articles/5af86b61004bdc
pure sh bible (📖 A collection of pure POSIX sh alternatives to external processes). - コマンドを使わずpure bashであれやこれやする一覧
https://github.com/dylanaraps/pure-bash-bible#get-the-directory-name-of-a-file-path
shell.how - インタラクティブにコマンド内容やオプションを解説してくれる
https://www.shell.how/
なぜシェルスクリプトで高度なデータ管理にSQLiteを使うべきなのか? ~ UNIX/POSIXコマンドの欠点をSQLで解決する
https://qiita.com/ko1nksm/items/33ab7ced0f9f8acdff28
onceupon/Bash-Oneliner
https://github.com/onceupon/Bash-Oneliner
シェルスクリプトの表示を豊かにするgumの使い方
https://dev.classmethod.jp/articles/eetann-gum-suburi/
GoogleのShell Style Guideの邦訳
https://qiita.com/yabeenico/items/72b904d4bb0b6d732a86
シェルスクリプトでgetoptsで解析する引数をポジショナルな引数と混合して使えるようにする
https://rcmdnk.com/blog/2023/11/01/computer-bash/
Bashの文字列で特殊文字を使う方法
https://azisava.sakura.ne.jp/programming/0010.html
シェルスクリプトで関数をそのままサブコマンドとして使う
https://rcmdnk.com/blog/2024/09/08/computer-bash/#google_vignette
改めて整理する、コンソール・ターミナル・仮想コンソール・端末エミュレータ・擬似端末
https://www.kanzennirikaisita.com/posts/what-is-console-terminal-etc
ASCII control characters in my terminal (端末の ASCII 制御文字)
https://jvns.ca/blog/2024/10/31/ascii-control-characters/
yassinebenaid/bunster(シェルスクリプトをバイナリ形式に変換するツール:Go実装にトランスパイル)
https://github.com/yassinebenaid/bunster
# 謝辞
以下のサイトを中心に参考にさせて頂いております。
ありがとうございます。
日々是迷歩
http://papiro.hatenablog.jp/
俺的備忘録 〜なんかいろいろ〜
http://orebibou.com/
Qiita - 実用 awk ワンライナー
http://qiita.com/b4b4r07/items/45d34a434f05aa896d69
SlideShare - 検索:シェル芸
http://www.slideshare.net/search/slideshow?searchfrom=header&q=%E3%82%B7%E3%82%A7%E3%83%AB%E8%8A%B8