Unix 系統提供了豐富的命令列工具,但有時需要額外的指令碼來擴充套件其功能。本文將介紹一些實用的 Shell 指令碼,用於增強命令列操作,包括統計可執行命令數量、格式化文字以及最佳化檔案刪除和還原機制。這些指令碼旨在簡化常見任務,並提供更便捷的操作方式。透過這些指令碼,使用者可以更有效率地管理檔案、控制命令列輸出,並提升整體操作體驗。

改善Unix命令列的實用工具指令碼

在典型的Unix或Linux系統中,預設包含了數百個命令,當你將這些命令與其標誌(flags)以及透過管道(pipes)組合起來使用時,便會產生數百萬種不同的命令列操作方式。本章節將介紹一些實用的指令碼,它們能夠在不增加複雜度的情況下,為使用者提供更多有趣或實用的功能。

計算PATH中的可執行命令數量

在討論具體指令碼之前,先來看看一個額外的指令碼,它能夠告訴你當前PATH中包含了多少個命令。

#!/bin/bash
# 計算可執行命令數量:簡單的指令碼,用於統計當前PATH中可執行命令的數量
IFS=":"
count=0 ; nonex=0
for directory in $PATH ; do
  if [ -d "$directory" ] ; then
    for command in "$directory"/* ; do
      if [ -x "$command" ] ; then
        count="$(( $count + 1 ))"
      else
        nonex="$(( $nonex + 1 ))"
      fi
    done
  fi
done
echo "$count 個命令,$nonex 個不可執行的條目"
exit 0

內容解密:

  • 該指令碼首先將IFS(內部欄位分隔符)設為冒號(:),以便正確解析PATH環境變數中的目錄列表。
  • 接著,指令碼遍歷PATH中的每個目錄,並檢查目錄中的每個檔案是否可執行。
  • 如果檔案可執行,則增加count計數器;否則,增加nonex計數器。
  • 最後,輸出可執行命令的總數和不可執行的檔案數量。

各作業系統的命令數量比較

透過上述指令碼,可以統計出不同作業系統中預設的命令數量,如下表所示:

作業系統命令數量不可執行的條目
Ubuntu 15.04(包含所有開發者函式庫)3,1565
OS X 10.11(安裝了開發者選項)1,66311
FreeBSD 10.29544
Solaris 11.22,00315

Unix哲學與命令設計

Unix哲學強調命令應該只做一件事,並且做好。這種設計哲學使得每個功能都可以獨立地被修改和擴充套件,從而為所有使用該功能的應用程式提供新的能力。

#14 長行文字格式化工具

某些Unix系統可能不包含fmt命令,但我們可以利用nroff命令來實作類別似的功能。以下是一個簡單的指令碼,用於格式化長行文字。

#!/bin/bash
# fmt--文字格式化工具,作為nroff的包裝器
# 新增兩個有用的標誌:-w X 用於指定行寬,-h 用於啟用斷字功能以改善填充效果
while getopts "hw:" opt; do
  case $opt in
    h ) hyph=1 ;;
    w ) width="$OPTARG" ;;
  esac
done
shift $(($OPTIND - 1))
nroff << EOF
.ll ${width:-72}
.na
.hy ${hyph:-0}
.pl 1
\ $(cat "$@")
EOF
exit 0

內容解密:

  • 該指令碼使用getopts來解析命令列選項,包括-w用於指定行寬和-h用於啟用斷字功能。
  • 使用shift命令移除已解析的選項,以便處理剩餘的檔案名稱。
  • 指令碼利用here document向nroff提供必要的格式化命令。
  • nroff命令根據指定的寬度和斷字選項對輸入文字進行格式化。

使用範例

執行以下命令,可以對文字進行格式化,啟用斷字功能並指定最大寬度為50個字元。

$ fmt -h -w 50 input.txt

輸出結果如下:

So she sat on, with closed eyes, and half believed
herself in Wonderland, though she knew she had but
to open them again, and all would change to dull
reality--the grass would be only rustling in the
wind, and the pool rippling to the waving of the
reeds--the rattling teacups would change to tin-
kling sheep-bells, and the Queen's shrill cries
to the voice of the shepherd boy--and the sneeze
of the baby, the shriek of the Gryphon, and all
the other queer noises, would change (she knew) to
the confused clamour of the busy farm-yard--while
the lowing of the cattle in the distance would
take the place of the Mock Turtle's heavy sobs.

最佳化使用者指令:檔案備份與復原機制

在 Unix 系統中,使用者經常會遇到意外刪除檔案或資料夾而無法復原的問題。為瞭解決這個問題,我們可以設計一個名為 newrm 的指令碼,自動將刪除的檔案備份到一個隱藏的 .deleted-files 目錄中。

實作 newrm 指令碼

以下是一個 Bash 指令碼的範例,用於實作檔案刪除前的備份功能:

#!/bin/bash
# newrm--A replacement for the existing rm command.
# This script provides a rudimentary unremove capability by creating and
# utilizing a new directory within the user's home directory. It can handle
# directories of content as well as individual files. If the user specifies
# the -f flag, files are removed and NOT archived.

archivedir="$HOME/.deleted-files"
realrm="$(which rm)"
copy="$(which cp) -R"

if [ $# -eq 0 ] ; then 
  exec $realrm 
fi

flags=""
while getopts "dfiPRrvW" opt
do
  case $opt in
    f ) exec $realrm "$@" ;; 
    * ) flags="$flags -$opt" ;; 
  esac
done
shift $(( $OPTIND - 1 ))

if [ ! -d $archivedir ] ; then
  if [ ! -w $HOME ] ; then
    echo "$0 failed: can't create $archivedir in $HOME" >&2
    exit 1
  fi
  mkdir $archivedir
  chmod 700 $archivedir 
fi

for arg
do
  newname="$archivedir/$(date "+%S.%M.%H.%d.%m").$(basename "$arg")"
  if [ -f "$arg" -o -d "$arg" ] ; then
    $copy "$arg" "$newname"
  fi
done

exec $realrm $flags "$@" 

內容解密:

  1. archivedirrealrm 的設定:首先,指令碼定義了備份目錄 $HOME/.deleted-files 和真實的 rm 命令路徑。這樣做的目的是將刪除的檔案備份到指定目錄,並確保呼叫真正的 rm 命令來刪除檔案。
  2. 選項解析:指令碼使用 getopts 解析使用者傳入的選項。如果使用者指定了 -f 選項,則直接呼叫真實的 rm 命令來強制刪除檔案,而不進行備份。
  3. 備份目錄的建立與許可權設定:如果備份目錄不存在,指令碼會建立它並設定許可權為 700,確保只有使用者本人可以存取該目錄。
  4. 檔案備份邏輯:對於每個需要刪除的檔案或目錄,指令碼會將其複製到備份目錄中,並在檔案名稱前加上時間戳,以避免同名檔案被覆寫。
  5. 呼叫真實的 rm 命令:最後,指令碼呼叫真實的 rm 命令來刪除指定的檔案或目錄。

使用與測試 newrm 指令碼

為了使用這個指令碼,你需要在你的 shell 組態檔案中新增一個別名,將 rm 指向 newrm 指令碼。例如,在 Bash 或 Korn shell 中,你可以新增以下別名:

alias rm='/path/to/newrm'

測試 newrm 指令碼的結果如下:

$ ls ~/.deleted-files
ls: /Users/taylor/.deleted-files/: No such file or directory
$ newrm file-to-keep-forever
$ ls ~/.deleted-files/
51.36.16.25.03.file-to-keep-forever

這個結果表明,雖然檔案 file-to-keep-forever 已從當前目錄中刪除,但它被秘密地備份到了 .deleted-files 目錄中。

最佳化檔案還原指令碼:unrm 的深度解析與實作

在前一章節中,我們探討瞭如何利用 newrm 指令碼建立一個隱藏的已刪除檔案目錄。本章節將進一步深入討論如何編寫 unrm 指令碼,以便從這個已刪除檔案目錄中還原檔案。

核心挑戰與設計考量

  1. 多重匹配處理:當使用者輸入的檔案名稱存在多個匹配版本時,指令碼需要能夠列出所有匹配結果並讓使用者選擇正確的版本進行還原。
  2. 時間戳記處理:指令碼需要解析檔案名稱中的時間戳記資訊,並將其轉換為易讀的日期和時間格式。
  3. 檔案大小與目錄內容統計:對於檔案,指令碼需要顯示其大小;對於目錄,則需要統計其包含的檔案數量。

指令碼實作詳解

#!/bin/bash
# unrm--從已刪除檔案存檔中搜尋指定的檔案或目錄。
# 如果有多個匹配結果,則顯示按時間戳記排序的結果列表,並讓使用者指定要還原的版本。

archivedir="$HOME/.deleted-files"
realrm="$(which rm)"
move="$(which mv)"
dest=$(pwd)

if [ ! -d $archivedir ] ; then
  echo "$0: 沒有已刪除檔案目錄:無內容可還原" >&2
  exit 1
fi

cd $archivedir

# 若未提供引數,則顯示已刪除檔案存檔的內容
if [ $# -eq 0 ] ; then
  echo "您的已刪除檔案存檔內容(按日期排序):"
  ls -FC | sed -e 's/\([[:digit:]][[:digit:]]\.\)\{5\}//g' \
              -e 's/^/ /'
  exit 0
fi

# 否則,必須有使用者指定的模式來進行匹配
matches="$(ls -d *"$1" 2> /dev/null | wc -l)"

if [ $matches -eq 0 ] ; then
  echo "在已刪除檔案存檔中找不到 \"$1\" 的匹配結果。" >&2
  exit 1
fi

if [ $matches -gt 1 ] ; then
  echo "在存檔中找到多個與 \"$1\" 匹配的檔案或目錄:"
  index=1
  for name in $(ls -td *"$1")
  do
    datetime="$(echo $name | cut -c1-14 | \
                awk -F. '{ print $5"/"$4" at "$3":"$2":"$1 }')"
    filename="$(echo $name | cut -c16-)"
    
    if [ -d $name ] ; then
      filecount="$(ls $name | wc -l | sed 's/[^[:digit:]]//g')"
      echo " $index) $filename (包含 ${filecount} 個專案,刪除於 $datetime)"
    else
      size="$(ls -sdk1 $name | awk '{print $1}')"
      echo " $index) $filename (大小 = ${size}Kb,刪除於 $datetime)"
    fi
    
    index=$(( $index + 1 ))
  done
  
  echo ""
  /bin/echo -n "應還原哪個版本的 \"$1\"?(輸入 '0' 離開)[1] : "
  read desired
  
  # 使用者輸入驗證與還原邏輯
  if [ ! -z "$(echo $desired | sed 's/[[:digit:]]//g')" ] ; then
    echo "$0: 使用者取消還原:無效輸入。" >&2
    exit 1
  fi
  
  if [ ${desired:=1} -ge $index ] ; then
    echo "$0: 使用者取消還原:索引值太大。" >&2
    exit 1
  fi
  
  if [ $desired -lt 1 ] ; then
    echo "$0: 使用者取消還原。" >&2
    exit 1
  fi
  
  restore="$(ls -td1 *"$1" | sed -n "${desired}p")"
  
  if [ -e "$dest/$1" ] ; then
    echo "\"$1\" 已存在於此目錄中。無法覆寫。" >&2
    exit 1
  fi
  
  /bin/echo -n "正在還原檔案 \"$1\" ..."
  $move "$restore" "$dest/$1"
  echo "完成。"
  
  /bin/echo -n "是否刪除其他版本的 \"$1\"?[y] "
  read answer
  
  if [ ${answer:=y} = "y" ] ; then
    $realrm -rf *"$1"
    echo "已刪除。"
  else
    echo "其他版本保留。"
  fi
else
  # 若只有一個匹配結果,直接還原
  if [ -e "$dest/$1" ] ; then
    echo "\"$1\" 已存在於此目錄中。無法覆寫。" >&2
    exit 1
  fi
  
  restore="$(ls -d *"$1")"
  /bin/echo -n "正在還原檔案 \"$1\" ... "
  $move "$restore" "$dest/$1"
  echo "完成。"
fi

exit 0

程式碼解密:

  1. archivedir 與相關命令初始化:定義了已刪除檔案的儲存目錄,並取得 rmmv 命令的完整路徑。
  2. 引數檢查與多重匹配處理:檢查使用者是否提供了搜尋條件,並根據條件匹配已刪除檔案。若有多個匹配結果,則列出並提示使用者選擇。
  3. 時間戳記解析與檔案資訊顯示:使用 awkcut 解析檔案名稱中的時間戳記,並顯示檔案或目錄的相關資訊(大小或包含的檔案數量)。
  4. 還原邏輯與使用者互動:根據使用者的選擇執行還原操作,並提供刪除其他版本檔案的選項。

使用範例與注意事項

  • 直接執行 unrm:列出所有已刪除檔案。
  • unrm filename:搜尋並還原指定的檔案或目錄。
  • 多重匹配情況:若有多個匹配結果,使用者需選擇正確的版本進行還原。

強化檔案刪除管理:記錄與還原機制

在 Linux 系統中,檔案刪除是一項常見的操作,但有時會因誤刪而導致資料遺失。本篇文章將介紹兩個 shell 指令碼,分別用於還原已刪除的檔案(unrm)及記錄檔案刪除操作(logrm),並對其進行深入分析與改進建議。

還原已刪除檔案:unrm 指令碼詳解

功能概述

unrm 是一個用於還原已刪除檔案的指令碼。它透過將刪除的檔案暫時移至特定目錄,實作檔案的還原功能。

程式碼結構

#!/bin/bash
# unrm--A simple file undelete utility
archive="/path/to/deleted/files/archive"
if [ $# -eq 0 ] ; then
    echo "Contents of your deleted files archive (sorted by date):"
    ls -lt "$archive"
    exit 0
fi
...

#### 內容解密:

  1. 檢查輸入引數:若未提供任何引數,則列出已刪除檔案存檔目錄中的內容。
  2. 搜尋符合條件的檔案:若提供檔案名稱,則嘗試還原該檔案或顯示多個符合條件的檔案供使用者選擇。
  3. 還原邏輯:根據使用者的選擇,將選定的檔案從存檔目錄移回原位置。

改善方向

  1. 新增引數控制:增加 -l 引數以還原最新版本,或 -D 引數以刪除多餘備份。
  2. 定期清理機制:利用 cron 任務定期清理過期的已刪除檔案,以節省磁碟空間。

記錄檔案刪除操作:logrm 指令碼解析

功能概述

logrm 是一個包裝 rm 命令的指令碼,用於記錄所有檔案刪除請求,並可選擇是否靜默執行刪除操作。

程式碼結構

#!/bin/bash
# logrm--Logs all file deletion requests unless the -s flag is used
removelog="/var/log/remove.log"
if [ $# -eq 0 ] ; then
    echo "Usage: $0 [-s] list of files or directories" >&2
    exit 1
fi
if [ "$1" = "-s" ] ; then
    shift
else
    echo "$(date): ${USER}: $@" >> $removelog
fi
/bin/rm "$@"
exit 0

#### 內容解密:

  1. 檢查輸入引數:若無引數,則顯示用法並離開。
  2. 靜默模式判斷:若第一個引數為 -s,則跳過記錄刪除請求的操作。
  3. 記錄刪除操作:將刪除請求的時間、使用者及刪除檔案列表記錄到 /var/log/remove.log
  4. 執行刪除操作:呼叫 /bin/rm 執行實際的檔案刪除。

安全與改進考量

  1. 日誌檔案許可權管理:需確保 /var/log/remove.log 的寫入許可權,以避免安全風險。
  2. 避免使用 setuid:因其可能引入安全漏洞,建議改用其他方法,如設定日誌檔案為 append-only 或使用 syslog 機制。
  3. 改進記錄方式:可考慮使用 logger 命令將刪除記錄寫入系統日誌,以增強安全性和集中管理。