GNU/Linux >> LINUX-Kenntnisse >  >> Linux

So erstellen Sie Bash-Skripte mit externen Variablen und eingebetteten Skripten

Es gibt Zeiten, in denen ein Skript nach Informationen fragen muss, die nicht in einer Konfigurationsdatei gespeichert werden können, oder wenn die Anzahl der Auswahlmöglichkeiten es Ihnen nicht erlaubt, alle Möglichkeiten anzugeben. Bash ist ziemlich gut darin, interaktive Skripte zu erstellen, um diese Art von Problemen anzugehen.

Idealerweise sollten Sie am Ende dieses Artikels in der Lage sein, Folgendes zu tun:

  • Schreiben Sie kleine Programme, die dem Nutzer Fragen stellen, und speichern Sie die Antworten (einschließlich sensibler wie Passwörter)
  • Daten aus Konfigurationsdateien mit anderen Programmen lesen
  • Erlauben Sie dem Skript, Fragen zu überspringen, wenn externe Variablen definiert sind
  • Und schreiben Sie als Bonus eine schöne Benutzeroberfläche (UI) mit Textdialogen

Beginnen Sie mit einem kleinen Skript, um über das RDP-Protokoll eine Verbindung zu einem Remote-Desktop herzustellen.

[Sie könnten auch gerne lesen:Bash für die Automatisierung verwenden ]

Fallstudie:Mit RDP eine Verbindung zu einem Remote-Server herstellen

Unter Linux gibt es viele RDP-Clients, und ein wirklich guter ist freerdp. Eine Möglichkeit, es aufzurufen, besteht darin, eine lange Reihe von Flags (mit verwirrenden Kurznamen) wie folgt zu übergeben:

/usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:REMOTE_USER /v:MACHINE /p:mynotsosecretpassword

Gibt es einen besseren Weg, dies zu tun?

Fragen stellen, lesen lernen

Also habe ich für einen ersten Versuch (Version 1) einen Shell-Wrapper um freerdp geschrieben, der nach dem Benutzer, dem Passwort und dem Remote-Computer fragt. Ich werde den in Bash integrierten Lesebefehl verwenden:

#!/bin/bash
# author Jose Vicente Nunez
# Do not use this script on a public computer.
tmp_file=$(/usr/bin/mktemp 2>/dev/null) || exit 100
trap '/bin/rm -f $tmp_file' QUIT EXIT INT
/bin/chmod go-wrx "${tmp_file}" > /dev/null 2>&1
read -r -p "Remote RPD user: " REMOTE_USER|| exit 100
test -z "$REMOTE_USER" && exit 100
read -r -s -p "Password for $REMOTE_USER: " PASSWD|| exit 100
test -z "$PASSWD" && exit 100
echo
echo > "$tmp_file"|| exit 100
read -r -p "Remote server: " MACHINE|| exit 100
test -z "$REMOTE_USER" && exit 100
/usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:"$REMOTE_USER" /v:"${MACHINE}" /p:"(/bin/cat ${tmp_file})"

Zum read (Zeile 7, 13) in eine Variable, sagen Sie einfach Variable lesen . Um es benutzerfreundlicher zu gestalten, übergeben Sie -p (einen benutzerdefinierten Prompt anzeigen) und -r (Lesen Sie umgekehrte Schrägstriche, wenn Sie einen Tippfehler machen).

read ermöglicht Ihnen auch, die Zeichen zu unterdrücken, die Sie auf dem Bildschirm schreiben. Die Option heißt -s (geheimer) Modus (Zeile 9).

Eine Sache, die mich stört, ist, dass jeder ein ps -ef ausführt kann mein Passwort auf der Kommandozeile sehen; Um dies zu vermeiden, speichere ich es in einer Datei und lese es dann mithilfe einer Subshell zurück, wenn xfreerdp es benötigt. Um zu vermeiden, dass mein Passwort auf der Festplatte herumliegt, speichere ich es außerdem in einer temporären Datei, die sicher entfernt wird, sobald das Skript beendet oder beendet ist.

Aber trotzdem ... dieses Skript stellt immer wieder einige Fragen. Gibt es eine Möglichkeit, es intelligenter zu machen?

Sie könnten einige der Standardeinstellungen, wie die Remote-Server, in einer Konfigurationsdatei speichern. Wenn Sie keine angeben, verwenden Sie die Standardeinstellungen.

Auch zum Thema Wiederverwendung von Code:Legen Sie die Logik zum Herstellen einer Verbindung zu einem Remote-Server in einer separaten Datei ab, falls Sie einen Teil dieser Logik in anderen ähnlichen Situationen wiederverwenden möchten. Die neue Bibliothek sieht also so aus:

#!/bin/bash
# author Jose Vicente Nunez
# Common logic for RDP connectivity
function remote_rpd {
    local remote_user=$1
    local pfile=$2
    local machine=$3
    test -z "$remote_user" && exit 100
    test ! -f "$pfile" && exit 100
    test -z "$machine" && exit 100
    /usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:"$remote_user" /v:"${machine}" /p:"(/bin/cat ${pfile})" && return 0|| return 1
}

Der RDP-Wrapper, Version 2 des ursprünglichen Skripts, ist jetzt viel einfacher:

#!/bin/bash
# author Jose Vicente Nunez
# Do not use this script on a public computer.
# shellcheck source=/dev/null.
. "rdp_common.sh"
tmp_file=$(/usr/bin/mktemp 2>/dev/null) || exit 100
trap '/bin/rm -f $tmp_file' QUIT EXIT INT
/bin/chmod go-wrx "${tmp_file}" > /dev/null 2>&1
read -r -p "Remote RPD user: " REMOTE_USER|| exit 100
read -r -s -p "Password for $REMOTE_USER: " PASSWD|| exit 100
echo
echo "$PASSWD" > "$tmp_file"|| exit 100
read -r -p "Remote server: " MACHINE|| exit 100
remote_rpd "$REMOTE_USER" "$tmp_file" "$MACHINE"

Wie sieht es also nach dieser Änderung aus?

$ ./kodegeek_rdp2.sh
Remote RPD user: jose
Password for jose: 
Remote server: myremotemachine.kodegeek.com

Es gibt noch Raum für Verbesserungen, also lesen Sie bitte weiter.

Geben Sie Benutzern immer die Wahl:Externe Variablen und mehr externe Programme

Angenommen, Sie verwenden Ihr Skript, um sich jeden Tag mit derselben Maschine zu verbinden. Es besteht die Möglichkeit, dass Sie Ihren Remote-Benutzer, Ihren Computer und nur das Kennwort von Zeit zu Zeit nicht ändern. Sie können also all diese Einstellungen in einer Konfigurationsdatei speichern, die nur vom aktuellen Nutzer und sonst niemandem gelesen werden kann:

(Beispiel für ~/.config/scripts/kodegeek_rdp.json )

{
    "machines": [
        {
            "name": "myremotemachine.kodegeek.com",
            "description": "Personal-PC"
        },
        {
            "name": "vmdesktop1.kodegeek.com",
            "description": "Virtual-Machine"
        }
    ],
    "remote_user": "jose@MYCOMPANY",
    "title" : "Remote desktop settings"
}

Ja, JSON ist nicht das beste Format für Konfigurationsdateien, aber dieses hier ist ziemlich klein. Beachten Sie außerdem, dass Sie jetzt mehr als einen Remote-Computer speichern können (verwenden Sie der Einfachheit halber nur den ersten).

Um davon zu profitieren, ändern Sie die Bibliothek (v2) so, dass sie so aussieht:

#!/bin/bash
# author Jose Vicente Nunez
# Common logic for RDP connectivity
if [[ -x '/usr/bin/jq' ]] && [[ -f "$HOME/.config/scripts/kodegeek_rdp.json" ]]; then
    REMOTE_USER="$(/usr/bin/jq --compact-output --raw-output '.remote_user' "$HOME"/.config/scripts/kodegeek_rdp.json)"|| exit 100
    MACHINE="$(/usr/bin/jq --compact-output --raw-output '.machines[0]| join(",")' "$HOME"/.config/scripts/kodegeek_rdp.json)"|| exit 100
    export REMOTE_USER
    export MACHINE
fi


function remote_rpd {
    local remote_user=$1
    local pfile=$2
    local machine=$3
    test -z "$remote_user" && exit 100
    test ! -f "$pfile" && exit 100
    test -z "$machine" && exit 100
    /usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:"$remote_user" /v:"${machine}" /p:"(/bin/cat ${pfile})" && return 0|| return 1
}

Ist Ihnen aufgefallen, dass ich nicht versucht habe, das Passwort aus einer Konfigurationsdatei zu lesen? Das ist der einzige Berechtigungsnachweis, den ich immer wieder fragen werde, es sei denn, er ist verschlüsselt. Die restlichen Werte erhalten Sie mit jq unter Verwendung einer Subshell.

Und natürlich gibt es hier eine neue Version (v3) des Skripts:

#!/bin/bash
# author Jose Vicente Nunez
# Do not use this script on a public computer.
# shellcheck source=/dev/null
. "rdp_common2.sh" 
tmp_file=$(/usr/bin/mktemp 2>/dev/null) || exit 100
trap '/bin/rm -f $tmp_file' QUIT EXIT INT
/bin/chmod go-wrx "${tmp_file}" > /dev/null 2>&1
if [ -z "$REMOTE_USER" ]; then
    read -r -p "Remote RPD user: " REMOTE_USER|| exit 100
fi
read -r -s -p "Password for $REMOTE_USER: " PASSWD|| exit 100
echo
echo "$PASSWD" > "$tmp_file"|| exit 100
if [ -z "$MACHINE" ]; then
    read -r -p "Remote server: " MACHINE|| exit 100
fi
remote_rpd "$REMOTE_USER" "$tmp_file" "$MACHINE"

Beachten Sie, dass Sie nicht mehr nach zwei Parametern fragen; nur das Passwort:

$ ./kodegeek_rdp2.sh 
Password for jose@MYCOMPANY: 

Gibt es irgendetwas anderes, was Sie tun können, um dieses Skript zu verbessern?

Ich möchte eine schöne Text-UI:Nichts geht über einen guten Dialog

So schreiben Sie ein interaktives Skript mit einem einfachen Tool namens Dialog. Es fordert den Benutzer auf, zwischen einer variablen Anzahl von Maschinen (abhängig von der Konfigurationsdatei) und natürlich dem Passwort zu wählen. Wenn der Remote-Benutzer jedoch für beide Computer derselbe ist (was normal ist, wenn Sie sich mit derselben Firma verbinden), wird er nicht jedes Mal nach den Informationen fragen.

Beachten Sie, dass Dialog nicht der einzige Spieler in der Stadt ist. Ich mag es einfach, weil es weit verbreitet ist und weil es einfach ist.

Unten ist Version 3 des Skripts. Es wird stark kommentiert. Sie können sehen, dass Dialog funktioniert, indem Sie entweder Variablen oder Dateien lesen, um Optionen zu aktivieren/deaktivieren. Probieren Sie es aus und führen Sie das Skript aus, um zu sehen, wie die einzelnen Teile zusammenpassen:

#!/bin/bash
# author Jose Vicente Nunez
# Do not use this script on a public computer.
# https://invisible-island.net/dialog/
SCRIPT_NAME="$(/usr/bin/basename "$0")"
DATA_FILE="$HOME/.config/scripts/kodegeek_rdp.json"
test -f "$DATA_FILE"|| exit 100
: "${DIALOG_OK=0}"
: "${DIALOG_CANCEL=1}"
: "${DIALOG_HELP=2}"
: "${DIALOG_EXTRA=3}"
: "${DIALOG_ITEM_HELP=4}"
: "${DIALOG_ESC=255}"
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

TITLE=$(/usr/bin/jq --compact-output --raw-output '.title' "$DATA_FILE")|| exit 100
REMOTE_USER=$(/usr/bin/jq --compact-output --raw-output '.remote_user' "$DATA_FILE")|| exit 100

# Choose a machine
MACHINES=$(
    tmp_file2=$(/usr/bin/mktemp 2>/dev/null) || declare tmp_file2=/tmp/test$$
    /usr/bin/jq --compact-output --raw-output '.machines[]| join(",")' "$DATA_FILE" > $tmp_file2|| exit 100
    declare -i i=0
    while read -r line; do
        machine=$(echo "$line"| /usr/bin/cut -d',' -f1)|| exit 100
        desc=$(echo "$line"| /usr/bin/cut -d',' -f2)|| exit 100
        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
# shellcheck disable=SC2086
/usr/bin/dialog \
    --clear \
    --title "$TITLE" \
    --radiolist "Which machine do you want to use?" 20 61 2 \
    $MACHINES 2> ${tmp_file}
return_value=$?

case $return_value in
  "$DIALOG_OK")
    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

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

# Ask for the password
/bin/rm -f ${tmp_file}
/usr/bin/dialog \
  --title "$TITLE" \
  --clear  \
  --insecure \
  --passwordbox "Please enter your remote password for ${remote_machine}\n" 16 51 2> $tmp_file
return_value=$?
passwd=$(/bin/cat ${tmp_file})
/bin/rm -f "$tmp_file"
if [ -z "${passwd}" ]; then
  /usr/bin/dialog \
      --clear  \
    --title "Error, empty password" --clear "$@" \
           --msgbox "Empty password!" 15 30
  exit 100
fi

# Try to connect
case $return_value in
  "$DIALOG_OK")
    /usr/bin/mkdir -p -v "$HOME"/logs
    /usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:"$REMOTE_USER" /v:"${remote_machine}" /p:"${passwd}"| \
    /usr/bin/tee "$HOME"/logs/"$SCRIPT_NAME"-"$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

[ Erste Schritte mit Containern? Schauen Sie sich diesen kostenlosen Kurs an. Containerisierte Anwendungen bereitstellen:Eine technische Übersicht. ]

Abschluss

Das war eine Menge Boden in einem Artikel zu behandeln. Skripte wie das in diesem Artikel entwickelte vereinfachen Verbindungen und machen die Benutzeroberfläche für Benutzer einfacher. Hier sind die Dinge, die Sie gelernt haben:

  • Sie können Bashs eingebautes read verwenden Befehl, um Informationen von Ihren Benutzern zu erhalten.
  • Sie können prüfen, ob sich wiederholende Informationen bereits verfügbar sind, um ein Ablesen aus der Umgebung zu vermeiden.
  • Sie speichern keine Passwörter ohne Verschlüsselung. KeepPassXC und Vault sind hervorragende Tools, mit denen Sie verhindern können, dass vertrauliche Informationen an den falschen Stellen hartcodiert werden.
  • Sie möchten eine schönere Benutzeroberfläche, also können Sie Dialog und andere leicht verfügbare Tools verwenden, um dies zu erreichen.
  • Bestätigen Sie Ihre Eingaben immer und überprüfen Sie sie auf Fehler.

Linux
  1. Bash + So beenden Sie beide Male das sekundäre Skript und das Hauptskript

  2. So erstellen und rufen Sie Funktionen in Bash auf

  3. So erstellen und wenden Sie Patches in GIT mit dem Befehl „diff“ und „apply“ an

  4. So verfolgen Sie Python-Skripte mit trace.py

  5. Wie schreibe ich eine mehrzeilige Zeichenfolge mit Bash mit Variablen?

So erstellen Sie GUI-Dialogfelder in Bash-Skripten mit Whiptail in Linux

So debuggen Sie Bash-Skripte in Linux und Unix

So erstellen und führen Sie ein C-Programm mit Ubuntu 20.04 LTS aus

So erstellen Sie Dokumente mit Bash-Skripten

So erstellen und verwalten Sie Linux-Partitionen mit Parted

VMware:So erstellen Sie eine virtuelle Maschine und installieren das Gastbetriebssystem mit dem vSphere-Client