Linuxのbash

1.bash概要

Linuxのbashはforなどのループや変数が使えるなど、意外に便利である。

(1)いろいろ

・コメントは#
・複数の命令をつなげる場合は;  ※このあたりはPythonでも同じ
・Linuxでは、デフォルトでbashが使われる。
使えるシェルのリストの確認(?あってるかな?)は、以下

#cat /etc/shells
/bin/sh
/bin/bash
/bin/rbash
/bin/dash
(2)まずは作成してみよう

たとえば、以下のファイルを作る。test.sh

#!/bin/bash
echo "hello"

実行する

#chmod +x test.sh     #実行権限の付与
#./test.sh
hello

何点か補足する。
・冒頭にbashのパス(/bin/bash)を記載する。ちなみにこの1行目のことをシバンと言う。
・このパスについては、whichで確認しておくのが確実だ。

#which bash
/bin/bash
(3)作業時の注意点 WindowsでファイルをUpload

Windowsでファイルを作ってUploadしても、文字コードの関係でうまく実行できなかった。viで開いても同じなのだが、fileコマンドで見ると、ファイル形式が違うという。そして、実行してもエラーになる。
→解決策
Windowsでファイルを作ってLinuxに送る場合、UnicodeはUTF-8(BOM無)、改行コード LF onlyとする。こうしないと、Linux側でうまく実行されない。
まあ、ファイルをUploadするときは、TeraTermのSSH SCPがいいだろう。
http://sm.seeeko.com/archives/21206156.html

(4)historyコマンド

❶概要
・bashのコマンド履歴を表示する。
・ファイルとしては、 .bash_history
ファイルのありかは、ユーザのホームディレクトリ。
❷bashのhistoryを消す
・コマンドで実行

history -c

ただ、history -c はログインしたユーザが実行した履歴のみの削除かもしれないので、ユーザ毎に、以下もしておいたほうが安心かも。
・historyファイルを直接編集

vi ~/.bash_history

以下はごっそり消す方法

cat /dev/null > ~/.bash_history 

履歴はログアウト時に .bash_history に書き込まれるので、上記コマンドを実行直後は、ファイル
が存在しないであろう。
また、history -c をしても、.bash_historyが消えていない気がするが、単に書き込んでいないだけかもしれない。

2.文字の出力とecho

(1)基本

echoで文字を出力できる。どれでくくっても、同じ結果になる。基本は"でくくるのかな?

echo abc   # => abc
echo "abc" # => abc
echo 'abc' # => abc
(2)変数を出力

❶シェルで変数を使う。
めちゃくちゃ簡単。シェルに書かなくても、コマンド上に打ち込んでもいい
書き方は以下のように、 " と ’ と 何も囲わないというのがあるが、まあ、" がいいのではないか。

IP1=192.168.1.1
IP2="192.168.1.1"
IP3='192.168.1.1'
echo $IP1 # => 192.168.1.1
echo $IP2 # => 192.168.1.1
echo $IP3 # => 192.168.1.1

・以下のように、echoで出力する場合、シングルクォートで囲うと変数ではなく、そのまま文字列として表記される。

echo IP=$IP2 # => IP=192.168.1.1
echo "IP=$IP2" # => IP=192.168.1.1
echo 'IP=$IP2' # => IP=$IP2 ※変数ではなく文字として出力

・変数と普通の文字が混在する場合、{}で変数を囲うといいだろう。以下は囲わない場合と囲った場合の違い。

echo "IP=$IP2desu" # => IP=
echo "IP=${IP2}desu" # => IP=192.168.1.1desu

❸環境変数を消すときは、unset。このとき$はつけない。
# unset IP
# echo $IP

(3)echoでファイル出力

❶echoで新規ファイルを作成する

❷改行コードなどを入れる場合
改行コードなどを入れるには-eのオプションをつける。 
改行コードは\n、"や$などは\によって\"、\$とする。

echo -e "<html>\n<body>\n<form action=longin.php method=\"POST\">\n<div>" >login.php
(4)catでファイル出力

echoでファイルを作る場合、ソースファイルを整形しなくてはいけないのが面倒。そこで、以下が便利。
構文としては、以下で、ファイル名を指定する。終わりはEOF。

cat <<'EOF' > file.txt
I
am 
a
boy
EOF

このとき、EOFを'EOF'としてシングルクォートで囲うと、$などの変数展開がされない。
なので、たとえば、以下のようなスクリプトもそのままbashで流し込むことができる。

#index.cgiを作成
cat <<'EOF' > index.cgi  #EOFを'でクォートしないと、$が変数として展開されてしまう。
#!/bin/perl
use CGI;
use CGI::Cookie;

my $cgi = new CGI;
my $url =$cgi->param('url');

if ($ENV{'QUERY_STRING'} ne '') {
  if ($url ne '') {
    print "Location: $url\n\n";
  }
  print $cgi->header;
}
else{

print $cgi->header;

my %cookies = fetch CGI::Cookie;
if (exists $cookies{'SID'}) {
    $value = $cookies{'SID'}->value;
    if ($value eq 12345){
        print "success";
    }
}else{
    print "hello";
}
}
EOF

3.if文やループ

(1)if文

and条件やofもつけられる。
基本構文は、こんな感じ。

if
then
elif
else
fi

❶文字が一致するか
・文字を比較するときの構文であるが、 という2重カッコがよく使われる。これ以外にも[] やtestを使うなどもできる。
・[[の後ろに空白がいるなど、ちょっとしたことで動かないので注意
以下は、IPアドレスを入力させて、1.1.1.1であればcorrect そうでなければnot correctと表示するもの。

#!/bin/bash
read -p "Tell me IP address:" ip
if [[ $ip = "1.1.1.1" ]]
then
  echo "correct"
else
  echo "not correct"
fi

❺ファイルが存在するか
以下は、a2.shが存在するかを確認する。-f でファイル名を指定する。

#!/bin/bash
#read -p "Tell me IP address:" ip
if [[ -f "/root/a2.sh" ]]
then
  echo "correct"
else
  echo "not correct"
fi

※ディレクトリが存在するかを調べる場合は、-fの代わりに-dを使う

(2)forによるループ

❶Cのような書き方
これはCのような書き方で、個人的にはわかりやすい。bashでしかできない構文のようだ。
sleep 5として、5秒間隔で実行させている。カッコを2つ、((で囲う点に注意

#!/bin/bash
for ((i=1 ; i<5 ; i++))
do
  echo $i
done

❷シンプルな書き方

#!/bin/bash
for i in {1..4}; do
  echo $i
done

❸配列の値を回す

#!/bin/bash
student=(Ito Suzuki Takana)
for name in ${student[@]}; do
  echo "name=$name"
done

❹ループを回してコマンドを実行
以下は、user1からuser5をループで削除する場合
forでiの値を1~5で変化させる。そして、変数をコマンドに入れるには、${i}という記述をする。

for i in {1..5};do
  userdel -rf user${i}
done

→watchコマンドやwhileでもできるようす。
watch -n 5 echo "Hello"

4.配列

配列も使えるので、とても便利だ。

(1)配列への入力と表示

❶配列に値を入れて、出力するいくつかのパターンを例示する。

#aという配列に以下の5つの値を入れる
a=(1 ab 2 cd 3)
a=("1" "ab" "2" "cd" "3")       #ダブルクォーテションで閉じた方が無難だろう。
#配列の1つ目、2つ目を表示
echo ${a[0]}      # => a
echo ${a[1]}      # => ab
#配列を指定するとどうなるか
echo $a            # =>1 先頭の1のみ出力
echo ${a[@]}    # =>1 ab 2 cd 3 全て出力
#配列の要素数を出力
echo ${#a[@]}  # =>5 配列の要素の数を出力

❷dateという結果を配列に入れて、時刻だけを取り出した様子。結構使えそう。

date  # => Wed 21 Oct 2020 10:50:10 PM UTC
b=(`date`) 
echo ${b[4]} # => 10:51:15
(2)配列の値の変更

❶配列に値を追加

a=(1 ab 2 cd 3)
a+=(ef)        # => efを末尾に追加
echo ${a[@]}   # => 1 ab 2 cd 3 ef

❷値の変更
1つ目の値を6に変更する。

a=(1 ab 2 cd 3)
a[0]=6         #a[0]の値を6に
echo ${a[0]}   # => 6

5.コマンドラインで引数を渡す

(1)コマンドラインの引数

実行時にコマンドラインの引数を渡す
・以下のように、pg1.shを実行時に、コマンドライン引数として10、20、30を渡したとする。
# ./pg1.sh 10 20 30

すると、プログラムの中では $1 $2 $3 にそれぞれ値が入れられる。
pg1.shを以下とする。

echo "number1=$1"
echo "number2=$2"
echo "number3=$3"

出力結果は以下

number1=10
number2=20
number3=30
(2)ユーザに入力を求める

・以下は、ユーザにIPアドレスを入力してもらい、それを変数に入れるシェルであるa.sh。今回はechoで出力もしている。

#!/bin/bash
read -p "Tell me IP address:" ip
echo "IP address=$ip"

結果は以下

# ./a.sh
Tell me IP address:10.1.1.1
IP address=10.1.1.1

6.ファイルを読み込んでの処理

(1)まあ、いろいろやり方はある

以下の方法がシンプルかと。
❶テキストデータを読み込み
テキストにあるデータを1行ずつ読み込み、ループ処理をする。lineというのは単なる変数(だと思う)。違う文字でもできるはず。
以下は、単に表示するだけ

#!/bin/bash
cat ./list.txt | while read line
do
  echo $line
done

もちろん、$lineを変数として、コマンドの処理もできる。その場合は、 ${line}として、他の文字と混在しないようにした方が無難だ。

7.その他

(1)標準出力関連

❶結果をファイルに出力する

ls -l > a.txt

❷標準出力とファイルの両方
・teeを使う

ls -l | tee a.txt 

・または、以下のようにパイプでつなぐことが多いと思う

ls -l | tee a.txt | more
ls -l | tee a.txt | grep word
(2)いろいろ

❶コマンドの実行結果を変数に入れる
以下のようにバッククォートを使い、その中にコマンドを入れる。
VPC_ID=`command`
echo ${VPC_ID}

❷JSON形式の操作
・事前にJSON形式の処理をするために、jqをインストール
yum -y install jq
・たとえば、以下のようなJson形式のファイルがあった場合、VPCの中のVpcidを取得したいとする。

{
    "Vpc": {
        "VpcId": "vpc-xxx",
        "InstanceTenancy": "default",
        "CidrBlo・・

コマンドは以下。-rでダブルクォートを外している。

jq -r '.Vpc | .VpcId'

または、以下がいいかも

jq -r '.RouteTables[].RouteTableId'`