顯示具有 bash 標籤的文章。 顯示所有文章
顯示具有 bash 標籤的文章。 顯示所有文章

2025/02/12

Linux Find

之前客戶 server 遇到單一目錄檔案過大問題,使用 rm -f * 或者是砍掉整個目錄都遇到 arguments 過多問題,最終使用 find 指令解決,有在使用 Linux 的人一定知道 find 的強大,在這邊作把常用的案例濃縮筆記一下。

範例

一般

find . -type f -name "lebron*" # 搜尋 lebron 為首的檔案
find . -type f -iname "lebron*" # 搜尋 lebron 為首的檔案(不區分大小寫)
find . -type d -name "lebron*" # 搜尋 lebron 為首的目錄

時間類

參數 說明 單位 範例 說明
atime 檔案最後存取的時間 天 find /path -atime -7 找出 7 天內被存取過的檔案
ctime 檔案的 inode 最後變更時間 天 find /path -ctime +30 找出 30 天前 inode 變更的檔案(如權限修改)
mtime 檔案內容的最後修改時間 天 find /path -mtime 0 找出今天內修改過內容的檔案
amin 檔案最後存取的時間 分鐘 find /path -amin -60 找出 60 分鐘內被存取過的檔案
cmin 檔案的 inode 最後變更時間 分鐘 find /path -cmin +120 找出 120 分鐘前 inode 變更的檔案
mmin 檔案內容的最後修改時間 分鐘 find /path -mmin -30 找出 30 分鐘內修改過內容的檔案

-time

指令 查詢範圍 檔案舉例(假設今天是 2024/02/21)
-mtime +3 4 天前或更早 ✅ 2024/02/17、✅ 2024/02/16
-mtime 3 剛好 3 天前 ✅ 2024/02/18
-mtime -3 最近 3 天內(包含今天) ✅ 2024/02/21、✅ 2024/02/20、✅ 2024/02/19
-mtime 0 今天(00:00 之後) ✅ 2024/02/21(今天)

min

指令 查詢範圍 檔案舉例(假設現在是 12:00 PM)
-mmin +30 31 分鐘前或更早 ✅ 11:29 AM、✅ 11:00 AM
-mmin 30 剛好 30 分鐘前 ✅ 11:30 AM
-mmin -30 最近 30 分鐘內(含現在) ✅ 12:00 PM、✅ 11:50 AM、✅ 11:45 AM
-mmin 0 最近 1 分鐘內(剛改變) ✅ 12:00 PM

空間類

參數 說明 單位 範例 說明
-size 指定檔案大小 512-byte blocks (預設) find /path -size +100M 找出大於 100MB 的檔案
-empty 找出空檔案或空目錄 N/A find /path -empty 找出空檔案或空目錄
-maxdepth 限制搜尋深度 N/A find /path -maxdepth 2 -size +1G 限制搜尋到最多 2 層目錄內,找出大於 1GB 的檔案
-type f 限制搜尋結果為檔案 N/A find /path -type f -size -50k 找出小於 50KB 的檔案
-type d 限制搜尋結果為目錄 N/A find /path -type d -size +10M 找出超過 10MB 的目錄(通常為不常見情況)

-size 常見用法:

  • find /path -size +1G → 找出 大於 1GB 的檔案
  • find /path -size -500k → 找出 小於 500KB 的檔案
  • find /path -size 100M → 找出 剛好 100MB 的檔案
  • find /path -size +100M -size -500M → 找出大小介於 100MB ~ 500MB 之間的檔案

-size 可用單位:

  • c → bytes(例如 -size +1024c 找出大於 1024 bytes 的檔案)
  • k → KB(例如 -size -500k 找出小於 500KB 的檔案)
  • M → MB(例如 -size +10M 找出大於 10MB 的檔案)
  • G → GB(例如 -size +1G 找出大於 1GB 的檔案)

顯示

格式 說明 範例
%p 檔案的完整路徑 ./test.txt
%f 檔案的名稱(不包括路徑) test.txt
%s 檔案的大小(以 byte 為單位) 1024
%M 檔案的權限(如 ls -l 顯示) -rw-r--r--
%u 檔案的擁有者 user1
%g 檔案的群組 group1
%T@ 檔案的最後修改時間,Unix 時間戳 1592390505.000000000
%TY-%Tm-%Td 檔案的修改日期(年-月-日) 2024-02-12
%TH:%TM:%TS 檔案的修改時間(時:分:秒) 14:30:15
%p 檔案的路徑 ./folder/test.txt
%k 檔案的大小(以 1KB 為單位,四捨五入) 1
%l 檔案是符號連結時,顯示其指向的檔案路徑 ./link_to_file.txt
%c 檔案的創建時間(僅支援某些檔案系統) 2024-02-10 12:00:00

顯示檔案名稱與大小(以 KB 為單位)

find . -type f -printf "%s KB\t%p\n"
1024 KB    ./file1.txt
2048 KB    ./file2.log

顯示檔案的修改日期與名稱

find . -type f -printf "%TY-%Tm-%Td %p\n"
2024-02-12 ./file1.txt
2024-02-12 ./file2.log

顯示檔案權限、擁有者、群組、大小和名稱

find . -type f -printf "%M %u %g %8s %p\n"
-rw-r--r-- user1 group1    1024 ./file1.txt
-rw-r--r-- user2 group2    2048 ./file2.txt

執行類

-printf

指令 說明 範例 解釋
-exec 對符合條件的檔案執行指令 find /path -type f -name "*.log" -exec rm {} \; 刪除所有 .log 檔案
-exec (多個參數) 傳遞多個參數到指令 find /path -type f -name "*.txt" -exec mv {} /backup/ \; 移動 .txt 檔案到 /backup/
-exec (多檔案批次處理) 用 + 來一次傳遞多個檔案 find /path -type f -name "*.jpg" -exec ls -lh {} + 列出 .jpg 檔案詳細資訊(批次傳遞參數)
-execdir 在檔案所在目錄執行指令 find /path -type f -name "*.bak" -execdir rm {} \; 在檔案所在目錄刪除 .bak 檔案

刪除所有 .log 檔案:

find /var/log -type f -name "*.log" -exec rm {} \;

批次刪除 .tmp 檔案(避免多次執行 rm):

find /tmp -type f -name "*.tmp" -exec rm {} +

找出 .sh 檔案並修改權限:

find /scripts -type f -name "*.sh" -exec chmod +x {} \;

計算 .log 檔案總大小:

find /var/log -type f -name "*.log" -exec du -ch {} + | tail -n 1

-delete:

指令 說明 範例 解釋
-delete 刪除符合條件的檔案 find /path -type f -name "*.log" -delete 刪除所有 .log 檔案
-delete (空目錄) 刪除空目錄 find /path -type d -empty -delete 刪除所有空目錄
-delete (限制層級) 限制搜尋深度刪除 find /path -maxdepth 1 -type f -name "*.tmp" -delete 只刪除 /path 下的 .tmp 檔案,不影響子目錄

其他

參數 說明 範例 解釋
-user 搜尋指定使用者擁有的檔案 find /path -user username 找出屬於指定使用者的檔案
-group 搜尋指定群組擁有的檔案 find /path -group groupname 找出屬於指定群組的檔案
-perm 根據檔案權限搜尋檔案 find /path -perm 755 找出擁有 755 權限的檔案
-newer 搜尋修改時間在指定檔案之後的檔案 find /path -newer file1.txt 找出在 file1.txt 之後被修改過的檔案
! -newer 搜尋修改時間在指定檔案之前的檔案 find /path ! -newer file1.txt 找出在 file1.txt 之前被修改過的檔案
-maxdepth 限制搜尋的最大目錄層級 find /path -maxdepth 2 只搜尋最多 2 層目錄深度的檔案
-mindepth 限制搜尋的最小目錄層級 find /path -mindepth 2 只搜尋從第 2 層開始的檔案
-print0 輸出檔案名稱並以 null 字元結尾,用於處理空格 find /path -print0 方便與 xargs -0 搭配使用,避免檔名有空格
-0 接受 null 字元分隔的檔案名稱 find /path -print0 | xargs -0 rm 用於 xargs,確保檔名包含空格的情況下也能正確處理
-iname 不區分大小寫的檔案名稱搜尋 find /path -iname "*.jpg" 搜尋所有 .jpg 檔案,不區分大小寫
-empty 搜尋空的檔案或目錄 find /path -empty 找出所有空的檔案或目錄
-exec 執行指定的命令來處理找到的檔案 find /path -name "*.log" -exec ls -lh {} \; 對每個找到的檔案執行 ls -lh
-exec + 批次處理所有符合條件的檔案,提高效率 find /path -name "*.log" -exec ls -lh {} + 批次執行命令,將多個檔案一次傳遞給命令處理
-prune 排除特定目錄,使其不被搜尋 find /path -path "/path/exclude" -prune -o -name "*.txt" -print 排除 /path/exclude 目錄,只搜尋其他 .txt 檔案
-follow 跟隨符號連結 find /path -follow -name "*.txt" 跟隨符號連結,搜尋 .txt 檔案

2024/05/22

Different copy behavior in rsync scp cp and mv

在 Linux 的環境內,我們常常會需要搬移目錄或檔案,不管是在內部或者是 remote,搬移的時候尾巴有沒有 / 這個符號在幾個常用跟搬移有關的指令實際行為有很大的差異,以下面的結構舉例。

src/first.txt
src/second/second.txt

dest/

src 是來源目錄,dest 是目標目錄。

cp

command result
cp -rv src dest/ dest/src/first.txt
dest/src/second/second.txt
cp -rv src/ dest/ dest/src/first.txt
dest/src/second/second.txt
cp -rv src/* dest/ dest/first.txt
dest/second/second.txt

cp 這個指令不會隨 / 有所影響,要不要 src 這層取決於你有沒有指定他以下的內容進行複製。

scp

command result
scp -r src dest/ dest/src/first.txt
dest/src/second/second.txt
scp -r src/ dest/ dest/src/first.txt
dest/src/second/second.txt
scp -r src/* dest/ dest/first.txt
dest/second/second.txt

scp 的複製行為跟 cp 是一模一樣的。

rsync

command result
rsync -rv src dest/ dest/src/first.txt
dest/src/second/second.txt
rsync -rv src/ dest/ dest/first.txt
dest/second/second.txt
rsync -rv src/* dest/ dest/first.txt
dest/second/second.txt

rsync 是會受到 src 後方有沒有 / 影響結果的。

mv

command result
mv src dest/ dest/src/first.txt
dest/src/second/second.txt
mv src/ dest/ dest/src/first.txt
dest/src/second/second.txt
mv src/* dest/ dest/first.txt
dest/second/second.txt

總結來講,cp、scp、mv 這三個搬運的行為是一致的,會有不同影響的只有 rsync,所以使用 rsync 時要比較注意一點,也可以說你要不包含目錄的話,四個指令就都加 /*,要包含的話就尾巴不要 /,這樣比較好記。

2024/03/25

Delete None Keeping Files Part II

之前寫過這篇 Delete None Keeping Files,在某個目錄依照建日保留最新的 N 個檔案,有個新改良的寫法或許更好懂。

#!/bin/bash

KEEP=10
DIRECTORY=/data/

find "$DIRECTORY" -maxdepth 1 -type f -name "*.log" -printf "%T@ %p\n" | sort -r | tail -n +$(($KEEP+1)) | awk '{print $NF}' | xargs rm -f

來解釋各段參數:

  • find "$DIRECTORY":尋找目標目錄
  • -maxdepth 1:只找該目錄第一層
  • -type f:只找尋檔案,不列入目錄
  • -name '*.log':找尋副檔名為 log 的檔案
  • -printf "%T@ %p\n":使用 printf 輸出,輸出方式為建立的時間戳 + 檔名
  • sort -r:依照時間戳逆排序
  • tail -n +$(($KEEP+1)):列出你要保留的檔案數以外的檔案
  • awk '{print $NF}':只抓出檔案路徑
  • xargs rm -f:刪除該檔案

這樣的寫法應該是萬無一失了。

2022/10/10

Delete None Keeping Files By Command find

之前寫過 Delete None Keeping File 這篇,用來刪除固定數量的保存檔案,使用的是 ls,今天使用 find 來達成一樣的效果。

首先我們來建二十個以 0.05 秒間距不同的檔案:

for i in {1..20}; do touch "${i}.log"; sleep 0.05; done

列出檔案一下目前排序是這樣。

0 Oct 15 23:56 1.log
0 Oct 15 23:56 10.log
0 Oct 15 23:56 11.log
0 Oct 15 23:56 12.log
0 Oct 15 23:56 13.log
0 Oct 15 23:56 14.log
0 Oct 15 23:56 15.log
0 Oct 15 23:56 16.log
0 Oct 15 23:56 17.log
0 Oct 15 23:56 18.log
0 Oct 15 23:56 19.log
0 Oct 15 23:56 2.log
0 Oct 15 23:56 20.log
0 Oct 15 23:56 3.log
0 Oct 15 23:56 4.log
0 Oct 15 23:56 5.log
0 Oct 15 23:56 6.log
0 Oct 15 23:56 7.log
0 Oct 15 23:56 8.log
0 Oct 15 23:56 9.log

用預設的 find 查詢一下檔案內容。

find . -type f -name "*.log"

./10.log
./8.log
./19.log
./14.log
./13.log
./9.log
./2.log
./20.log
./1.log
./17.log
./6.log
./15.log
./12.log
./16.log
./4.log
./5.log
./11.log
./7.log
./18.log
./3.log

有人可能會說那利用 sort 不就可以自然排序正確的數字,沒錯,但那是因為我們的檔名自然有序,若遇到實際上沒有這樣結構的檔案,或者萬一臨時修改了檔名造成新排序不是我們要的結果就會出問題,所以最保險的方法還是利用日期去自然排序。

我們利用 -printf 這個指令來達到目的。

find . -type f -name "*.log" -printf "%T+ %p\n" | sort
2022-10-15+23:56:20.1777708840 ./1.log
2022-10-15+23:56:20.2297708850 ./2.log
2022-10-15+23:56:20.2817708850 ./3.log
2022-10-15+23:56:20.3337708860 ./4.log
2022-10-15+23:56:20.3857708860 ./5.log
2022-10-15+23:56:20.4457708870 ./6.log
2022-10-15+23:56:20.5057708880 ./7.log
2022-10-15+23:56:20.5697708880 ./8.log
2022-10-15+23:56:20.6337708890 ./9.log
2022-10-15+23:56:20.6817708890 ./10.log
2022-10-15+23:56:20.7377708900 ./11.log
2022-10-15+23:56:20.7977708910 ./12.log
2022-10-15+23:56:20.8457708910 ./13.log
2022-10-15+23:56:20.9057708920 ./14.log
2022-10-15+23:56:20.9577708920 ./15.log
2022-10-15+23:56:21.0217708930 ./16.log
2022-10-15+23:56:21.0977708940 ./17.log
2022-10-15+23:56:21.1617708950 ./18.log
2022-10-15+23:56:21.2137708950 ./19.log
2022-10-15+23:56:21.2617708960 ./20.log

printf 詳細用法可以參考 How to Use find -printf in Linux?,非常的強大,其他工作就可以接續 Delete None Keeping File 處理。

find . -type f -name "*.log" -printf "%T+ %p\n" | sort -r | awk '{print $NF}' | tail -n +11
./10.log
./9.log
./8.log
./7.log
./6.log
./5.log
./4.log
./3.log
./2.log
./1.log

上方指令搭配 xargs rm -f 就可以只保留最新的十份檔案。

2022/06/10

Shell Script With Optional Arguments

我們在操作 Linux 上的 script 時,常常會使用一些 optional arguments,例如:

ls -l
grep -iR --include *.php

我們自己製作腳本時可以使用 getopts 以及 shift 達到這樣的效果,以一個安裝系統的腳本舉例。

install.sh

#/bin/bash

install_nginx=0
install_mysql=0
nginx_port=80
mysql_port=3306

while getopts "nmP:p:" OPTION; do
  case "$OPTION" in
  n)
    install_nginx=1
    ;;
  m)
    install_mysql=1
    ;;
  p)
    nginx_port="$OPTARG"
    ;;
  P)
    mysql_port="$OPTARG"
    ;;
  esac
done

echo "Install basic"

if [ "$install_nginx" == 1 ]; then
  echo "Install nginx with port $nginx_port"
fi

if [ "$install_mysql" == 1 ]; then
  echo "Install mysql with port $mysql_port"
fi
bash install.sh

# Install basic

bash install.sh -n

# Install basic
# Install nginx with port 80

bash install.sh -n -p 8888 -m
# Install basic
# Install nginx with port 8888
# Install mysql with port 3306

上面的方法只能帶入單一字符,所以像是 port 這種常見共用的文字只能用大小寫或者不同字符取代,彈性比較少,使用 shift 可以解決這個問題。

#/bin/bash

install_nginx=0
install_mysql=0
nginx_port=80
mysql_port=3306

while [ "$1" != "" ]; do
  case "$1" in
  --nginx)
    install_nginx=1
    ;;
  --nginx-port)
    shift
    nginx_port="$1"
    ;;
  --mysql)
    install_mysql=1
    ;;
  --mysql-port)
    shift
    mysql_port="$1"
    ;;
  esac
  shift
done

echo "Install basic"

if [ "$install_nginx" == 1 ]; then
  echo "Install nginx with port $nginx_port"
fi

if [ "$install_mysql" == 1 ]; then
  echo "Install mysql with port $mysql_port"
fi
bash install.sh --nginx
# Install basic
# Install nginx with port 80

bash install.sh --nginx --nginx-port 8888
# Install basic
# Install nginx with port 8888

bash install.sh --nginx --mysql --mysql-port 3307
# Install basic
# Install nginx with port 80
# Install mysql with port 3307

reference

2022/04/07

Makefile

如果我們的專案有指令是會重複執行的,透過 Makefile 可以集成內容,最方便的是多組合依序執行。

建立一個檔案名稱為 Makefile:

基本應用

init:
        echo first command
        echo second command
$ make init
echo first command
first command
echo second command
second command

安靜輸出

如果你不希望 terminal 印出你下的命令內容,有幾種寫法可以避免。

.SILENT: init

init:
        echo first command
        echo second command
.SILENT: #影響全部

init:
        echo first command
        echo second command
init:
        @echo first command
        @echo second command

多重步驟

first_check:
        echo first check

second_check:
        echo second check

init: first_check second_check
        echo init
$ make init
echo first check
first check
echo second check
second check
echo init
init

變數

file=default

init:
        @echo file is $(file)
$ make
file is default

$ make file=new
file is new

變數可以經由帶入方式覆蓋。

2022/01/27

Special Variable In Linux

Linux 有許多特殊變數可以使用,要用的時候常常不記得,用這篇統整記錄一下,以後用到其他的會持續更新。

name description
-G 存在﹐並且由 GID 所執行的行程所擁有。
-L 存在﹐並且是 symbolic link 。
-O 存在﹐並且由 UID 所執行的行程所擁有。
-S 存在﹐並且是一個 socke 。
-b 存在﹐並且是 block 檔案﹐例如磁碟等。
-c 存在﹐並且是 character 檔案﹐例如終端或磁帶機。
-d 存在﹐並且是一個目錄。
-e 存在。
-f 存在﹐並且是一個檔案。
-g 存在﹐並且有 SGID 屬性。
-k 存在﹐並且有 sticky bit 屬性。
-p 存在﹐並且是用於行程間傳送資訊的 name pipe 或是 FIFO。
-r 存在﹐並且是可讀的。
-s 存在﹐並且體積大於 0 (非空檔)。
-u 存在﹐並且有 SUID 屬性。
-w 存在﹐並且可寫入。
-x 存在﹐並且可執行。
-z 空字串
-n 非空字串

整數比較

name description example
-eq 等於 [ integer1 -eq integer2 ]
-ne 不等於 [ integer1 -ne integer2 ]
-lt 小於 [ integer1 -lt integer2 ]
-gt 大於 [ integer1 -gt integer2 ]
-le 小於或等於 [ integer1 -le integer2 ]
-ge 大於或等於 [ integer1 -ge integer2 ]

特殊變量

name description
$1~$9 函數的第一個到第9個的參數
$0 函數所在的腳本名
$# 函數的參數總數
$* 函數的全部參數
$? 顯示最後命令的退出狀態,0 表示沒有錯誤,其他表示有錯誤
$$ 腳本運行的當前 process id
$! 上一個執行命令的 process id
$_ 在此之前執行的命令或者腳本的最後一個參數
test.sh
#!/bin/bash

echo $0
echo $1
echo $2
echo $#
echo $*
echo $$
bash test.sh a b
test.sh
a
b
2
a b
54877

$? 這個預設變數非常實用,可以用來做許多沒有給足判斷的工具當作一種判斷依據,以這篇 tmux 的範例為例,tmux 的 has-session 並非回傳 boolean,而是 session 存在則無反應,session 不存在時噴出 can’t find session 訊息,因此你無法下這種語法:

if tmux has-session -t "$name"; then
    # do something
fi

取而代之的方法就如同我範例那樣抓最後一個 process 的 exit code 當作判斷依據。

$$ 則是如果該 bash 為 daemon 時,可以將 pid 存放在某個檔案內,這樣就可以偵測如果該 pid 有檔案時先 kill 該 process 之後在重啟,否則同一時間就會同時有兩個 daemon 在執行。

2021/11/18

update multiple package to latest at once

npm 有一個指令可以偵測套件是否有出新的可以更新,我故意裝了 jquery@2 以及 bootstrap@4。

npm ls
test@1.0.0 D:\node_test
├── bootstrap@4.6.1
└── jquery@2.2.4

我們來執行更新檢查:

npm outdated
Package    Current  Wanted  Latest  Location                Depended by
bootstrap    4.6.1   4.6.1   5.1.3  node_modules/bootstrap  node_test
jquery       2.2.4   2.2.4   3.6.0  node_modules/jquery     node_test

bootstrap 跟 jquery 都有新版本可以更新,更新指令如下:

npm install jquery@latest bootstrap@latest

如果之前就有加入 package.json 的話則不用指定 --save 或是 --save-dev,一兩個套件可能還好,假設今天有十幾個要一次更新就累了,而 npm 本身沒有提供全部更新到最新版的指令,npm update 跑的是在 package.json 的板號下的最新版,所以跨了版號就不執行了。

npmjs 當然有套件可以輔助這件事,像是 npm-check,但我們也可以透過 shell script 自己處理,以下示範 awk 以及 cut 的用法。

# 指令一,利用 awk
npm outdated | sed 1d | awk -F ' ' '{print sprintf("%s@latest", $1)}' | xargs npm install
npm outdated | sed 1d | awk -F ' ' '{print sprintf("%s@%s", $1, $4)}' | xargs npm install

# 指令二,利用 cut
npm outdated | sed 1d | cut -d' ' -f1 | xargs -I {} echo '{}@latest' | xargs npm install

2021/05/05

Vagrant provision with docker and docker-compose install

vagrant 提供了 provision 初始化功能,可以類似 Dockerfile 在初始化 vagrant 的時候就把想要安裝的內容處理好,下面示範一下如何安裝一個乾淨的 ubuntu 後把 docker 以及 docker-compose 一併安裝。

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"

  config.vm.provider "virtualbox" do |vb|
    vb.memory = "1024"
  end

  config.vm.provision "shell", inline: <<-SHELL
    docker -v &> /dev/null

    if [[ $? != 0 ]]; then
      echo "start install docker"
      curl -sL https://get.docker.com/ | sudo bash
      sudo usermod -aG docker vagrant
      docker -v
    else
      echo "docker installed"
    fi

    docker-compose -v &> /dev/null

    if [[ $? != 0 ]]; then
      echo "start install docker-compose"
      sudo curl -sL "https://github.com/docker/compose/releases/download/1.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
      sudo chmod +x /usr/local/bin/docker-compose
      sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
      docker-compose -v
    else
      echo "docker-compose installed"
    fi
  SHELL
end

由於 provision 有可能重複使用,所以最一些執行檢查是必要的,這邊我檢查已安裝的技巧是使用 -v 這個參數,如果沒有安裝,系統會報錯,因此 $? 會非 0,藉此判斷有沒有安裝過。

2020/12/22

Delete None Keeping Files

當我們在備份文件的時候,通常只保留一定的數量,例如說 10 天份,我們可能會這樣寫:

$ find PATH_TO_FILES -mtime +30 | xargs rm -f &> /dev/null
$ find PATH_TO_FILES -mtime +30 -exec rm {} \ &> /dev/null;

這兩種寫法結果一樣,都是會刪除建立超過三十天以上的檔案,不過這會有個問題,假設今天因為某些因素備份指令中斷了,但 crontab 依舊在跑刪除指令,若 op 沒有及時發現,備份檔終究會被刪光,因此另一種寫法我認為是比較好的

我們先製造六個檔案

i=1; while [ $i -lt 7 ]; do touch $i; ((i++)); done

$ ll -d ./*
-rw-r--r-- 1 root root 0 12月 22 11:45 ./1
-rw-r--r-- 1 root root 0 12月 22 11:45 ./2
-rw-r--r-- 1 root root 0 12月 22 11:45 ./3
-rw-r--r-- 1 root root 0 12月 22 11:45 ./4
-rw-r--r-- 1 root root 0 12月 22 11:45 ./5
-rw-r--r-- 1 root root 0 12月 22 11:45 ./6

假設我們只要保留 5 個檔案,指令就是:

$ ls -ltd ./* | tail -n +6 | awk '{print $NF}' | xargs rm -f &> /dev/null

-d 過濾了 ./ 以及 ../,-t 會照時間排序,tail -n +6 就是拉出超過 n 數量的結果,要保留 5 份的話要填入 6,所以檔名 1-6 時 6 會被篩選出來,awk '{print $NF}' 會印出篩選出來資訊的尾端,也就是檔名,最後就用 xargs rm -f 刪除該檔案,這樣寫的話不管時間的影響該備份目錄永遠會留下你指定的檔案數量。

2020/12/15

Required Input On Bash

在寫 shell script 的時候有時候必須填寫必要參數,如果想要 ui friendly 的話可以使用 prompt 的互動方式請 user 填寫。

ask.sh
#!/bin/bash  
  
while [[ -z ${name} ]]; do  
  read -rp "your name: " name  
done  
  
while [[ -z ${age} ]]; do  
  read -rp "your age: " age  
done  
  
echo "Hello ${name^^}, your age is ${age}"

上方的執行過程如下:

$ /bin/bash ask.sh
your name: chan wu
your age: 40
Hello CHAN WU, your age is 40

2018/07/04

sh 分析 Apache access log

最近開始學習 server 管理,嘗試學習各種跟 server 有關的工具跟 knowhow,有個網站的簡易 shell script 語法可以快速做一些事情,把他寫成一個小工具來使用

apache_logger.sh
#!/bin/bash

command=$1
log=$2
commands=(
ip
access
loading
long
flow
404
)
commands_description=(
'ip 取得前十名 access 最多的 IP 位址'
'access 取得前十名 access 最多的網頁'
'loading 取得前十名 Loading 最大的頁面'
'long 取得前十名 User access 最久的頁面'
'flow 取得 access log 平均流量 (GB)'
'404 取得所有 404 Link'
)

function show_commands() {
    printf "用法 - sh loger.sh 命令 log位置\n\n"
    for (( i = 0; i < ${#commands_description[@]}; i++ )); do
        echo ${commands_description[$i]}
    done
    exit
}

if [[ -z $command && -z $log ]]; then
    show_commands
fi

if [[ -z $command ]]; then
    echo "Please input command"
    exit
fi

if [[ -z $log ]]; then
    echo "Please input log path"
    exit
fi

if [[ ! -f $log ]]; then
    echo "Log file is not exist"
    exit
fi

in_command=false
for (( i = 0; i < ${#commands[@]}; i++ )); do
    if [[ $command == ${commands[$i]} ]]; then
        in_command=true
    fi
done

if [[ $in_command = false ]]; then
    printf "Invalid command\n\n"
    show_commands
fi

case $command in
    'ip')
        cat $log |awk '{print $1}'|sort|uniq -c|sort -nr|head -10
        ;;
    'access')
        cat $log |awk '{print $11}'|sort|uniq -c|sort -nr|head -10
        ;;
    'loading')
        cat $log |awk '($NF > 60 && $7~/\.php/){print $7}'|sort -n|uniq -c|sort -nr|head -10
        ;;
    'long')
        cat $log |awk  '($7~/\.php/){print $NF " " $1 " " $4 " " $7}'|sort -nr|head -10
        ;;
    'flow')
        cat $log |awk '{sum+=$10} END {print sum/1024/1024/1024}'
        ;;
    '404')
        awk '($9 ~/404/)' $log | awk '{print $9,$7}' | sort
        ;;
esac

參考網站:快速分析 Apache 的 access log,抓出前十大網站流量兇手

2017/12/24

tmux

Common Shortcuts

<prefix> means ctrl + b.

# start new session
$ tmux new -s myname

# list sessions
$ tmux ls

# attach to session
$ tmux a -t 0

# rename session
$ tmux rename-session -t 0 NEW_NAME

# kill session
$ tmux kill-session -t 0

Windows Control

  • <prefix> c — Create window
  • <prefix> w — List windows
  • <prefix> n — Next window
  • <prefix> p — Previous window
  • <prefix> f — Find window
  • <prefix> , — Name window
  • <prefix> & — Kill window
  • <prefix> [ — Check history

Pane Control

  • <prefix> % — Vertical split
  • <prefix> " — Horizontal split
  • <prefix> o — Swap panes
  • <prefix> q — Show pane numbers
  • <prefix> x — Kill pane
  • <prefix> q — Show pane numbers (press number key to switch to that pane)
  • <prefix> { — Move the current pane left
  • <prefix> } — Move the current pane right
  • <prefix> z — Toggle pane zoom

Resize Pane

  • <prefix> : resize-pane -D (Resizes the current pane down)
  • <prefix> : resize-pane -U (Resizes the current pane upward)
  • <prefix> : resize-pane -L (Resizes the current pane left)
  • <prefix> : resize-pane -R (Resizes the current pane right)
  • <prefix> : resize-pane -D 20 (Resizes the current pane down by 20 cells)
  • <prefix> : resize-pane -U 20 (Resizes the current pane upward by 20 cells)
  • <prefix> : resize-pane -L 20 (Resizes the current pane left by 20 cells)
  • <prefix> : resize-pane -R 20 (Resizes the current pane right by 20 cells)
  • <prefix> : resize-pane -t 2 20 (Resizes the pane with the id of 2 down by 20 cells)
  • <prefix> : resize-pane -t -L 20 (Resizes the pane with the id of 2 left by 20 cells)

Start With Configuration

#!/bin/bash
tmux -2 new-session \; \
send-keys ''cd /var/www/test; vim'' C-m \; \
split-window -h \; \
send-keys 'top''top' C-m \; \
resize-pane -R 30 \; \
split-window -v \; \
send-keys ''while true; do date; sleep 1; done'' C-m \; \

My Config

套件

  • tpm 套件管理
  • tmux-pain-control 提供便捷的快捷鍵來控制 tmux 面板的導航與大小調整
  • tmux-sensible 提供一組合理的預設設置,讓 tmux 更加易於使用,特別是對新手友好
  • tmux-copycat 讓你在 tmux 中進行文字搜尋,並能夠快速複製選中的文字

安裝套件管理

git clone https://github.com/tmux-plugins/tpm.git ~/.tmux/plugins/tpm

~/.tmux.conf

# ~/.tmux.conf
set -g default-terminal "xterm"
setw -g mode-keys vi
set -g pane-border-status top
set -g mouse on

# List of plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'dracula/tmux'
set -g @dracula-plugins "git cpu-usage ram-usage time"
set -g @dracula-show-powerline true
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'tmux-plugins/tmux-pain-control'
set -g @plugin 'tmux-plugins/tmux-copycat'

bind-key -T copy-mode-vi 'v' send -X begin-selection

run '~/.tmux/plugins/tpm/tpm'

~/.bashrc

# ~/.bashrc
alias tmux='tmux -2'

開啟 tmux 後執行 <prefix> + I。

Auto Start Config

#!/bin/sh

SERVICE=apache_watcher

tmux has-session -t $SERVICE

if [[ $? != 0 ]]; then
	tmux new -s $SERVICE -d

	tmux split-window -h
	tmux split-window -v -t $SERVICE:0.1
	tmux split-window -v -t $SERVICE:0.0

	tmux send-keys -t $SERVICE:0.0 'htop'  C-m
	tmux send-keys -t $SERVICE:0.2 'nload' C-m
	tmux send-keys -t $SERVICE:0.3 'sudo apachetop' C-m

	tmux clock-mode -t $SERVICE:0.1
	tmux resize-pane -R 30
fi

tmux a -t $SERVICE

Interesting Command

  • <prefix> : select-pane -T “NAME” - Set the title of the active pane
  • <prefix> : select-layout even-horizontal - Make horizontal pane widths equal
  • <prefix> : select-layout even-vertical - Make vertical pane heights equal
  • <prefix> : select-layout tiled - Arrange panes in a tiled layout
  • <prefix> space - Cycle through available pane layouts
  • <prefix> alt + arrow - Resize pane in the arrow’s direction
  • <prefix> : resize-pane -x 100 - Set the pane width to 100 cells
  • <prefix> : resize-pane -y 100 - Set the pane height to 100 cells

Reference Site

https://gist.github.com/MohamedAlaa/2961058
https://blog.htbaa.com/news/tmux-scripting

2017/08/07

Prompt In All Language

最近喜歡刻一些互動的小工具,程式碼將需求分析後在 terminal 與 user 互動,confirm 內容後動作是常見的,這邊紀錄一下三個語言的 prompt 方法

prompt.php
echo 'Are you sure? [y/n]: ';

$handle = fopen('php://stdin', 'r');
$answer = trim(fgets($handle));

if ($answer === 'y') {
    echo 'Yes!'.PHP_EOL;
} else {
    echo 'Canceled!'.PHP_EOL;
}

fclose($handle);
prompt.js
const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

rl.question('Are you sure? [y/n]: ', (answer) => {
    if (answer.trim() === 'y') {
        console.log('Yes!');
    } else {
        console.log('Canceled!');
    }

    process.exit();
});
prompt.sh
#!/bin/bash

read -p 'Are you sure? [y/n]: ' answer

if [[ $answer == 'y' ]]; then
    echo "Yes!"
else
    echo "Canceled!"
fi

2017/06/27

Random String

公司有一個需求想要產生 unique id,雖然 PHP 有 uniqid 可以使用,但他最基本的長度是 13 個字元,所以決定自己刻一個利用 0-9、a-z 產生的亂數密碼,會有 PHP、nodejs 以及 shell script 版本。

PHP
<?php

function code($length = 10)
{
    $codeString = '0123456789abcdefghijklmnopqrstubwxyz';
    $codeStringSplit = str_split($codeString);
    $codeStringSize = count($codeStringSplit);
    $code = '';

    for ($i = 0; $i < $length; ++$i) {
        $rand = mt_rand(0, $codeStringSize - 1);
        $code .= $codeStringSplit[$rand];
    }

    return $code;
}

for ($i = 0; $i < 10; $i++) {
    echo code().PHP_EOL;
}
node.js
function code(length) {
    var stringLength = length || 10;
    var codeString = '0123456789abcdefghijklmnopqrstubwxyz';
    var codeStringSplit = codeString.split('');
    var codeStringSize = codeStringSplit.length;
    var code = '';
    var min = 0;
    var max = codeStringSize - 1;

    for (var i = 0; i < stringLength; ++i) {
        var rand = Math.floor(Math.random() * (max - min + 1)) + min;

        code += codeStringSplit[rand];
    }

    return code;
}

for (var i = 0; i < 10; ++i) {
    console.log(code());
}
bash
#!/bin/bash

function code() {
    result=""
    length=10
    codeString="0123456789abcdefghijklmnopqrstubwxyz";
    declare -a array

    for (( i = 0; i < ${#codeString}; i++ )); do
        array+=(${codeString:$i:1})
    done

    if [[ -n $1 ]]; then
        declare -i length=$1
    fi

    max=$((${#array[@]} - 1))
    
    for (( i = 0; i < ${length}; i++ )); do
        rand=$(shuf -i 0-${max} -n 1)
        result="${result}${array[${rand}]}"
    done

    echo ${result}
}

for (( j = 0; j < 10; j++ )); do
    code
done