Wie kann ich nur bestimmte Befehle aus einem Bash-Skript drucken, während sie ausgeführt werden?


19

Ich habe ein Bash-Skript mit verschiedenen if-Anweisungen, die auf Kommandozeilenargumenten basieren, die ich beim Aufruf übergebe. Es ist hilfreich, eine Ausgabe darüber zu haben, welche Befehle ausgeführt werden, um den Fluss durch all diese if-Anweisungen zu bestätigen, aber meine aktuelle Lösung gibt mir zu viele Informationen.

Die Verwendung set -vim Skript war etwas hilfreich, um zu sehen, wie Befehle auf dem Bildschirm gedruckt wurden, während sie im Skript ausgeführt wurden. Allerdings erhalte ich zu viele Befehle. Es ist fast wie eine ganze Kopie des Skripts.

Ich möchte eine Ausgabe, die zeigt, welche Befehle ausgeführt werden, aber ich möchte keine Kommentare, neuen Zeilen, Ausdrücke in if-Anweisungen usw. sehen.

Gibt es eine Möglichkeit, alle möglichen Ausgaben, die mit der Option -v generiert wurden, zuerst durch einen regulären Ausdruck zu leiten, bevor sie gedruckt werden? Oder eine andere Lösung, mit der bash nur Befehle eines bestimmten "Typs" ausgibt (z. B. ausführbare Dateien und nicht nur bash-spezifische Anweisungen, Kommentare usw.).

[1] /programming/257616/sudo-changes-path-why war hier sehr hilfreich und hier habe ich den Vorschlag für die set -vVerwendung erhalten.

Bearbeiten :

Ein ähnliches (aber nicht identisches) Skript wie das, das ich verwende:

#!/bin/bash

#get verbose command output
set -v

env=$1

if [ "$env" == "dev" ]; then
    python ascript.py
fi

if [ "$env" == "prod" ]; then

    #launching in prod will most likely fail if not run as root. Warn user if not running as root.
    if [ $EUID -ne 0 ]; then
        echo "It doesn't look like you're running me as root. This probably won't work. Press any key to continue." > /dev/stderr
        read input
    fi

    #"stop" any existing nginx processes
    pkill -f nginx

    nginx -c `pwd`/conf/artfndr_nginx.conf

fi

Ich möchte nur 2 mögliche Sätze von Ausgabezeilen aus diesem Skript. Der Erste:

python ascript.py

Der Zweite:

pkill -f nginx
nginx -c /some/current/directory/conf/artfndr_nginx.conf

1
Natürlich können Sie es analysieren, aber wir können nicht anders, als wenn Sie uns das Skript zeigen und erklären, welche Teile der set -vAusgabe Sie möchten und welche nicht.
Terdon

Antworten:


12

Wenn ich komplexere Bash-Skripte schreibe, verwende ich eine kleine Funktion, um Befehle auszuführen, die auch die Befehle ausgeben, die in einer Protokolldatei ausgeführt werden:

runthis(){
    ## print the command to the logfile
    echo "$@" >> $LOG
    ## run the command and redirect it's error output
    ## to the logfile
    eval "$@" 2>> $LOG 
}

Dann führe ich in meinem Skript folgende Befehle aus:

runthis "cp /foo/bar /baz/"

Wenn Sie einen Befehl nicht drucken möchten, führen Sie ihn einfach normal aus.

Sie können den $LOGDateinamen entweder als Dateinamen festlegen oder ihn einfach entfernen und auf stdout oder stderr drucken.


+1 Außerdem konnte ich dies in meinem Skript ausführen, indem ich einfach "wichtige" Befehle mit einer kurzen Version der Funktion voranstellte, sodass die Zeilen so aussehen, als v python ascript.py
müssten sie nicht

@Trindaz die Anführungszeichen sind da, wenn Sie Variablen in Ihren Befehlen übergeben müssen, wenn die Variablen Leerzeichen enthalten, könnten Sie sonst Probleme haben.
Terdon

eval ..... || ok=1: wird nur dann auf "1" gesetzt, wenn "eval ..." fehlschlägt? Vielleicht meintest du "&&"? Und wenn Sie das gemeint haben, fügen Sie vor der Auswertungszeile ein "ok = 0" hinzu, damit es jedes Mal "zurückgesetzt" wird. Oder einfach "ok" in "error" umbenennen? anscheinend war das hier so gemeint. Also am Ende:eval "$@" 2>> "$LOG" && error=0 || error=1
Olivier Dulac

@OlivierDulac, in der von mir verwendeten Version habe ich eine okVariable, die das Skript stoppt, wenn ein Befehl fehlschlägt. Da das hier nicht relevant war, habe ich es entfernt aber vergessen das zu löschen || ok=1. Danke, jetzt behoben.
Terdon

Hervorragende Lösung! Ich hatte das zu entfernen , "rund um die eval - Anweisung, da der Befehl bereits durch umgeben ist "s
gromit190

11

Verwenden Sie eine Unterschale, dh:

( set -x; cmd1; cmd2 )

Beispielsweise:

( set -x; echo "hi there" )

druckt

+ echo 'hi there'
hi there

Ich bevorzuge dieses set -x; cmd; set +xaus mehreren Gründen. Erstens wird es nicht zurückgesetzt, set -xfalls es zuvor eingeschaltet war. Zweitens führt das Beenden des Skripts nicht dazu, dass Traps mit ausführlichen Einstellungen ausgeführt werden.
Oliver Gondža

2

Ich habe Methoden gesehen, die denen von @ terdon ähneln. Es ist der Anfang dessen, was höhere Programmiersprachen als Logger bezeichnen und als vollwertige Bibliotheken anbieten, wie beispielsweise log4J (Java), log4Perl (Perl) usw.

Sie können mit set -xBash etwas Ähnliches erreichen, wie Sie es bereits erwähnt haben, aber Sie können damit das Debuggen nur einer Teilmenge von Befehlen beschleunigen, indem Sie Codeblöcke wie folgt damit verbinden.

$ set -x; cmd1; cmd2; set +x

Beispiele

Hier ist ein einzeiliges Muster, das Sie verwenden können.

$ set -x; echo  "hi" ;set +x
+ echo hi
hi
+ set +x

Sie können sie für mehrere Befehle in einem Skript so umbrechen.

set -x
cmd1
cmd2
set +x

cmd3

Log4Bash

Die meisten Leute sind sich dessen nicht bewusst, aber Bash hat auch ein log4 *, Log4Bash . Wenn Sie bescheidenere Bedürfnisse haben, ist dies möglicherweise die Zeit wert, um es einzurichten.

log4bash ist ein Versuch, Bash-Skripte besser zu protokollieren (dh, die Protokollierung in Bash ist nicht so einfach).

Beispiele

Hier sind einige Beispiele für die Verwendung von log4bash.

#!/usr/bin/env bash
source log4bash.sh

log "This is regular log message... log and log_info do the same thing";

log_warning "Luke ... you turned off your targeting computer";
log_info "I have you now!";
log_success "You're all clear kid, now let's blow this thing and go home.";
log_error "One thing's for sure, we're all gonna be a lot thinner.";

# If you have figlet installed -- you'll see some big letters on the screen!
log_captains "What was in the captain's toilet?";

# If you have the "say" command (e.g. on a Mac)
log_speak "Resistance is futile";

Log4sh

Wenn Sie möchten, was ich als die volle Leistung eines log4 * -Frameworks einstufen würde, würde ich es mit Log4sh versuchen.

Auszug

log4sh wurde ursprünglich entwickelt, um ein Protokollierungsproblem zu lösen, das ich in einigen der Produktionsumgebungen hatte, in denen ich entweder zu viel oder zu wenig protokolliert habe. Insbesondere Cron-Jobs bereiteten mir mit ihren ständigen und nervigen E-Mails die größten Kopfschmerzen. Sie sagten mir, dass alles funktioniert hat oder dass nichts funktioniert hat, aber kein detaillierter Grund warum. Ich verwende jetzt log4sh in Umgebungen, in denen das Protokollieren von Shell-Skripten wichtig ist, ich aber mehr als nur ein einfaches "Hallo, behebe mich!" Art der Protokollmeldung. Wenn Ihnen das, was Sie sehen, gefällt oder Sie Verbesserungsvorschläge haben, schreiben Sie mir bitte eine E-Mail. Wenn genügend Interesse an dem Projekt besteht, werde ich es weiterentwickeln.

log4sh wurde unter der Bourne Again-Shell (/ bin / bash) unter Linux entwickelt, es wurde jedoch große Sorgfalt darauf verwendet, dass es unter der Standard-Bourne Shell von Solaris (/ bin / sh) funktioniert, da dies die primäre Produktion ist Plattform von mir selbst genutzt.

Log4sh unterstützt mehrere Shells, nicht nur Bash.

  • Borowski-Shell (sh)
  • BASH - GNU Bourne Again SHell (Bash)
  • DASH (Bindestrich)
  • Korn Shell (ksh)
  • pdksh - die Public Domain Korn Shell (pdksh)

Es wurde auch auf mehreren Betriebssystemen getestet, nicht nur auf Linux.

  • Cygwin (unter Windows)
  • FreeBSD (benutzerunterstützt)
  • Linux (Gentoo, RedHat, Ubuntu)
  • Mac OS X
  • Solaris 8, 9, 10

Die Verwendung eines log4 * -Frameworks benötigt einige Zeit zum Erlernen, es lohnt sich jedoch, wenn Sie höhere Anforderungen an die Protokollierung haben. Log4sh verwendet eine Konfigurationsdatei, in der Sie Appender definieren und die Formatierung für die Ausgabe steuern können.

Beispiel

#! /bin/sh
#
# log4sh example: Hello, world
#

# load log4sh (disabling properties file warning) and clear the default
# configuration
LOG4SH_CONFIGURATION='none' . ./log4sh
log4sh_resetConfiguration

# set the global logging level to INFO
logger_setLevel INFO

# add and configure a FileAppender that outputs to STDERR, and activate the
# configuration
logger_addAppender stderr
appender_setType stderr FileAppender
appender_file_setFile stderr STDERR
appender_activateOptions stderr

# say Hello to the world
logger_info 'Hello, world'

Wenn ich es jetzt starte:

$ ./log4sh.bash 
INFO - Hello, world

HINWEIS: Mit dem obigen Befehl wird der Appender als Teil des Codes konfiguriert. Wenn Sie dies möchten, können Sie es in eine eigene Datei log4sh.propertiesusw. extrahieren .

Weitere Informationen finden Sie in der Dokumentation zu Log4sh .


Vielen Dank für die hinzugefügten Notizen, aber das Hauptproblem, das ich dabei habe, sind alle setBefehle, die ich einführen muss, abwechselnd mit Kommentaren usw., sodass ich nur eine Funktion am Anfang meines Skripts habe, der ein einziger Funktionsaufruf vorangestellt ist Alle "wichtigen" Zeilen im Skript schienen mir vorerst ordentlicher. (einzelnes Zeichen, da die Funktion einen einzelnen
Zeichennamen

@Trindaz - Entschuldigung, ich hatte meine Antwort noch nicht beendet. Werfen Sie einen Blick auf log4bash, wenn Sie mehr als die von terdon bereitgestellte Funktion benötigen.
slm

1
@Trindaz - Ich mache von Zeit zu Zeit etwas Ähnliches. Der andere Ansatz, den ich verwendet habe, besteht darin, echomeine eigene Funktion einzuschließen mechound dann einen Schalter an das Programm zu übergeben, das als -vausführlich bezeichnet wird, wenn ich Dinge ausschalten möchte. Ich kann es auch mit einem Schalter für das 2. Argument steuern, der den Namen der Funktion angibt. Ich habe also zwei Achsen, um die Protokollierung zu steuern. Dies ist jedoch oft das Tor zu log4bash.
slm

1
@Trindaz set -xdruckt Befehle, sobald sie ausgeführt werden. Es werden keine Kommentare gedruckt. set -xist praktisch zum Debuggen (im Gegensatz zu dem, set -vwas nicht sehr nützlich ist). Zsh hat eine bessere Ausgabe set -xals bash. Beispielsweise wird angezeigt , welche Funktion gerade ausgeführt wird, und die Quellzeilennummer .
Gilles 'SO- hör auf böse zu sein'

Danke @ Gilles, das ist wahr, aber es gab mir die if Ausdruckserweiterungen, die in diesem Fall
übertrieben waren

1

Sie könnten trap DEBUGund dann die BASH_COMMANDVariable testen . Fügen Sie dies oben im Skript hinzu:

log() {
    case "$1" in
        python\ *)
            ;&
        pkill\ *)
            printf "%s\n" "$*"
            ;;
    esac
}

trap 'log "$BASH_COMMAND"' DEBUG

Der Code ist lesbar; Es wird nur geprüft, ob das erste Argument mit pythonoder beginnt pkill, und es wird gedruckt, wenn dies der Fall ist. Und die Falle verwendet BASH_COMMAND(die den Befehl enthält, der ausgeführt wird) als erstes Argument.

$ bash foo.sh dev
python ascript.py
python: can't open file 'ascript.py': [Errno 2] No such file or directory
$ bash foo.sh prod
It doesn't look like you're running me as root. This probably won't work. Press any key to continue.

pkill -f nginx
foo.sh: line 32: nginx: command not found

Beachten Sie, dass Sie bei caseVerwendung von Globs genauso einfach Folgendes tun können:

if [[ $1 =~ python|nginx ]]
then
    printf "%s" "$*"
fi

Und verwenden Sie reguläre Ausdrücke.


0

Dies ist eine überarbeitete Version von Steven Pennys netter Funktion. Es druckt seine Argumente in Farbe und zitiert sie nach Bedarf. Verwenden Sie diese Option, um die Befehle, die Sie verfolgen möchten, selektiv wiederzugeben. Da Anführungszeichen ausgegeben werden, können Sie gedruckte Zeilen kopieren und zur sofortigen erneuten Ausführung in das Terminal einfügen, während Sie ein Skript debuggen. Lesen Sie den ersten Kommentar, um zu erfahren, was ich geändert habe und warum.

xc() # $@-args
{
  cecho "$@"
  "$@"
}
cecho() # $@-args
{
  awk '
  BEGIN {
    x = "\047"
    printf "\033[36m"
    while (++i < ARGC) {
      if (! (y = split(ARGV[i], z, x))) {
        printf (x x)
      } else {
        for (j = 1; j <= y; j++) {
          printf "%s", z[j] ~ /[^[:alnum:]%+,./:=@_-]/ ? (x z[j] x) : z[j]
          if (j < y) printf "\\" x
        }
      }
      printf i == ARGC - 1 ? "\033[m\n" : FS
    }
  }
  ' "$@"
}

Beispielverwendung mit Ausgabe:

# xc echo "a b" "c'd" "'" '"' "fg" '' " " "" \# this line prints in green

echo 'a b' c\'d \' '"' fg '' ' ' '' '#' this line prints in green

a b c'd ' " fg # this line prints in green

Die zweite Zeile darüber wird in Grün gedruckt und kann kopiert werden, um die dritte Zeile zu reproduzieren.

Weitere Bemerkungen

@ Steven-Pennys Original-XC ist clever und er verdient alle Credits dafür. Allerdings habe ich einige Probleme bemerkt, aber ich konnte seinen Beitrag nicht direkt kommentieren, weil ich nicht genug Ruf habe. Daher habe ich eine vorgeschlagene Änderung an seinem Beitrag vorgenommen, die Überprüfer haben diese Änderung jedoch abgelehnt. Daher habe ich versucht, meine Kommentare als diese Antwort zu veröffentlichen, obwohl ich es vorgezogen hätte, die Antwort von Steve Penny selbst bearbeiten zu können.

Was ich für Steven-Pennys Antwort geändert habe

Behoben: Drucken von Null-Strings - sie wurden nicht gedruckt. Behoben: Drucken von Strings, die %- sie verursachten awk-Syntaxfehler. Ersetzt for (j in ...)durch, for (j = 0, ...)weil Ersteres die Reihenfolge der Array-Durchquerung nicht garantiert (dies hängt von der jeweiligen Implementierung ab). Oktalzahlen wurden aus Gründen der Portabilität um 0 erhöht.

Aktualisieren

Steven Penny's hat diese Probleme seitdem in seiner Antwort behoben, so dass diese Bemerkungen nur für die historische Aufzeichnung meiner Antwort bleiben. Weitere Informationen finden Sie im Abschnitt "Kommentare".


0

Sie können die Shell-Funktion "sh_trace" aus der POSIX- Bibliothek stdlib verwenden , um den Befehl in Farbe zu drucken, bevor Sie ihn ausführen . Beispiel:

Vorschau

Zugrunde liegende Awk-Funktion:

function sh_trace(ary,   b, d, k, q, w, z) {
  b = "\47"
  for (d in ary) {
    k = split(ary[d], q, b)
    q[1]
    if (d - 1)
      z = z " "
    for (w in q) {
      z = z (!k || q[w] ~ "[^[:alnum:]%+,./:=@_-]" ? b q[w] b : q[w]) \
      (w < k ? "\\" b : "")
    }
  }
  printf "\33[36m%s\33[m\n", z
  system(z)
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.