Linux中國

使用 dialog 和 jq 在 Linux 上編寫高效終端 TUI

為何選擇文字用戶界面(TUI)?

許多人每日都在使用終端,因此, 文字用戶界面 Text User Interface TUI)逐漸顯示出其價值。它能減少用戶輸入命令時的誤差,讓終端操作更高效,提高生產力。

以我的個人使用情況為例:我每日會通過家用電腦遠程連接到我使用 Linux 系統的實體 PC。所有的遠程網路連接都通過私有 VPN 加密保護。然而,當我需要頻繁重複輸入命令進行連接時,這種經歷實在令人煩躁。

於是,我創建了下面這個 Bash 函數,從而有所改進:

export REMOTE_RDP_USER="myremoteuser"
function remote_machine() {
  /usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:$REMOTE_RDP_USER /v:$1 /p:$2
}

但後來,我發現自己還是頻繁地執行下面這條命令(在一行中):

remote_pass=(/bin/cat/.mypassfile) remote_machine $remote_machine $remote_pass

這太煩了。更糟糕的是,我的密碼被明文存儲在我的電腦上(我雖然使用了加密驅動器,但這點依然令人不安)。

因此,我決定投入一些時間,編寫一個實用的腳本,從而更好地滿足我的基本需求。

我需要哪些信息才能連接到遠程桌面?

實際上,要連接到遠程桌面,你只需提供少量信息。這些信息需要進行結構化處理,所以一個簡單的 JSON 文件就能夠滿足要求:

{"machines": [
  {
  "name": "machine1.domain.com",
  "description": "Personal-PC"
  },
  {
  "name": "machine2.domain.com",
  "description": "Virtual-Machine"
  }
  ],
"remote_user": "MYUSER@DOMAIN",
"title" : "MY COMPANY RDP connection"
}

儘管在各種配置文件格式中,JSON 並非最佳選擇(例如,它不支持註解),但是 Linux 提供了許多工具通過命令行方式解析 JSON 內容。其中,特別值得一提的工具就是 jq。下面我要向你展示如何利用它來提取機器列表:

/usr/bin/jq --compact-output --raw-output '.machines[]| .name' 
  $HOME/.config/scripts/kodegeek_rdp.json) 
  "machine1.domain.com" "machine2.domain.com"

jq 的文檔可以在 這裡 找到。另外,你也可以直接將你的 JSON 文件複製粘貼到 jq play,試用你的表達式,然後在你的腳本中使用這些表達式。

既然已經準備好了連接遠程計算機所需的所有信息,那現在就讓我們來創建一個美觀實用的 TUI 吧。

Dialog 的幫助

Dialog 是那些你可能希望早些認識的、被低評估的 Linux 工具之一。你可以利用它構建出一個井然有序、簡介易懂,並且完美適用於你終端的用戶界面。

比如,我可以創建一個包含我喜歡的編程語言的簡單的複選框列表,且默認選擇 Python:

dialog --clear --checklist "Favorite programming languages:" 10 30 7
  1 Python on 2 Java off 3 Bash off 4 Perl off 5 Ruby off

我們通過這條命令向 dialog 下達了幾個指令:

  • 清除屏幕(所有選項都以 -- 開頭)
  • 創建一個帶有標題的複選框(第一個位置參數)
  • 決定窗口尺寸(高度、寬度和列表高度,共 3 個參數)
  • 列表中的每條選項都由一個標籤和一個值組成。

驚人的是,僅僅一行代碼,就帶來了簡潔直觀和視覺友好的選擇列表。

關於 dialog 的詳細文檔,你可以在 這裡 閱讀。

整合所有元素:使用 Dialog 和 JQ 編寫 TUI

我編寫了一個 TUI,它使用 jq 從我的 JSON 文件中提取配置詳細信息,並且使用 dialog 來組織流程。每次運行,我都會要求輸入密碼,並將其保存在一個臨時文件中,腳本使用後便會刪除這個臨時文件。

這個腳本非常基礎,但它更安全,也使我能夠專註於更重要的任務 ?

那麼 腳本 看起來是怎樣的呢?下面是代碼:

#!/bin/bash
# Author Jose Vicente Nunez
# Do not use this script on a public computer. It is not secure...
# https://invisible-island.net/dialog/
# Below some constants to make it easier to handle Dialog
# return codes
: ${DIALOG_OK=0}
: ${DIALOG_CANCEL=1}
: ${DIALOG_HELP=2}
: ${DIALOG_EXTRA=3}
: ${DIALOG_ITEM_HELP=4}
: ${DIALOG_ESC=255}
# Temporary file to store sensitive data. Use a 'trap' to remove
# at the end of the script or if it gets interrupted
declare tmp_file=$(/usr/bin/mktemp 2>/dev/null) || declare tmp_file=/tmp/test$$
trap "/bin/rm -f $tmp_file" QUIT EXIT INT
/bin/chmod go-wrx ${tmp_file} > /dev/null 2>&1
:<<DOC
Extract details like title, remote user and machines using jq from the JSON file
Use a subshell for the machine list
DOC
declare TITLE=$(/usr/bin/jq --compact-output --raw-output &apos;.title&apos; $HOME/.config/scripts/kodegeek_rdp.json)|| exit 100
declare REMOTE_USER=$(/usr/bin/jq --compact-output --raw-output &apos;.remote_user&apos; $HOME/.config/scripts/kodegeek_rdp.json)|| exit 100
declare MACHINES=$(
    declare tmp_file2=$(/usr/bin/mktemp 2>/dev/null) || declare tmp_file2=/tmp/test$$
    # trap "/bin/rm -f $tmp_file2" 0 1 2 5 15 EXIT INT
    declare -a MACHINE_INFO=$(/usr/bin/jq --compact-output --raw-output &apos;.machines[]| join(",")&apos; $HOME/.config/scripts/kodegeek_rdp.json > $tmp_file2)
    declare -i i=0
    while read line; do
        declare machine=$(echo $line| /usr/bin/cut -d&apos;,&apos; -f1)
        declare desc=$(echo $line| /usr/bin/cut -d&apos;,&apos; -f2)
        declare toggle=off
        if [ $i -eq 0 ]; then
            toggle=on
            ((i=i+1))
        fi
        echo $machine $desc $toggle
    done < $tmp_file2
    /bin/cp /dev/null $tmp_file2
) || exit 100
# Create a dialog with a radio list and let the user select the
# remote machine
/usr/bin/dialog 
    --clear 
    --title "$TITLE" 
    --radiolist "Which machine do you want to use?" 20 61 2 
    $MACHINES 2> ${tmp_file}
return_value=$?
# Handle the return codes from the machine selection in the
# previous step
export remote_machine=""
case $return_value in
  $DIALOG_OK)
    export remote_machine=$(/bin/cat ${tmp_file})
    ;;
  $DIALOG_CANCEL)
    echo "Cancel pressed.";;
  $DIALOG_HELP)
    echo "Help pressed.";;
  $DIALOG_EXTRA)
    echo "Extra button pressed.";;
  $DIALOG_ITEM_HELP)
    echo "Item-help button pressed.";;
  $DIALOG_ESC)
    if test -s $tmp_file ; then
      /bin/rm -f $tmp_file
    else
      echo "ESC pressed."
    fi
    ;;
esac

# No machine selected? No service ...
if [ -z "${remote_machine}" ]; then
  /usr/bin/dialog 
        --clear  
        --title "Error, no machine selected?" --clear "$@" 
        --msgbox "No machine was selected!. Will exit now..." 15 30
  exit 100
fi

# Send 4 packets to the remote machine. I assume your network
# administration allows ICMP packets
# If there is an error show  message box
/bin/ping -c 4 ${remote_machine} >/dev/null 2>&1
if [ $? -ne 0 ]; then
  /usr/bin/dialog 
        --clear  
        --title "VPN issues or machine is off?" --clear "$@" 
        --msgbox "Could not ping ${remote_machine}. Time to troubleshoot..." 15 50
  exit 100
fi

# Remote machine is visible, ask for credentials and handle user
# choices (like password with a password box)
/bin/rm -f ${tmp_file}
/usr/bin/dialog 
  --title "$TITLE" 
  --clear  
  --insecure 
  --passwordbox "Please enter your Windows password for ${remote_machine}n" 16 51 2> $tmp_file
return_value=$?
case $return_value in
  $DIALOG_OK)
    # We have all the information, try to connect using RDP protocol
    /usr/bin/mkdir -p -v $HOME/logs
    /usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:$REMOTE_USER /v:${remote_machine} /p:$(/bin/cat ${tmp_file})| 
    /usr/bin/tee $HOME/logs/$(/usr/bin/basename $0)-$remote_machine.log
    ;;
  $DIALOG_CANCEL)
    echo "Cancel pressed.";;
  $DIALOG_HELP)
    echo "Help pressed.";;
  $DIALOG_EXTRA)
    echo "Extra button pressed.";;
  $DIALOG_ITEM_HELP)
    echo "Item-help button pressed.";;
  $DIALOG_ESC)
    if test -s $tmp_file ; then
      /bin/rm -f $tmp_file
    else
      echo "ESC pressed."
    fi
    ;;
esac

你從代碼中可以看出,dialog 預期的是位置參數,並且允許你在變數中捕獲用戶的回應。這實際上使其成為編寫文本用戶界面的 Bash 擴展。

上述的小例子只涵蓋了一些部件的使用,其實還有更多的文檔在 官方 dialog 網站上。

Dialog 和 JQ 是最好的選擇嗎?

實現這個功能可以有很多方法(如 Textual,Gnome 的 Zenity,Python 的 TKinker等)。我只是想向你展示一種高效的方式——僅用 100 行代碼就完成了這項任務。

確實,它並不完美。更具體地講,它與 Bash 的深度集成使得代碼有些冗長,但仍然保持了易於調試和維護的特性。相比於反覆複製粘貼長長的命令,這無疑是一個更好的選擇。

最後,如果你喜歡在 Bash 中使用 jq 處理 JSON,那麼你會對這個 jq 配方的精彩集合 感興趣的。

(題圖:MJ/a9b7f60a-02ec-4d3f-88ae-2321f49ac0e1)

via: https://fedoramagazine.org/writing-useful-terminal-tui-on-linux-with-dialog-and-jq/

作者:Jose Nunez 選題:lujun9972 譯者:ChatGPT 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國