• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

Gelöst bash - Dateien mit Leerstellen richtig übergeben

gorgonz

Hacker
Ich habe vorher natürlich recherchiert, es gibt Lösungen, aber ich habe sie nicht verstanden. Will mir ein Skript machen, dass mittels identify die orientation eines Bilds ermittelt und an Hand des Ergebnisses diese wahlweise dreht. Zum Ermitteln der orientation eine Funktion getorientation im Skript. Beim Aufruf von identify wird nur der erste String bis zum Leerzeichen genommen.

Vielleicht kann mir jemand weiter helfen, das mit Apostroph oder backslash-Apostroph hatte ich erfolglos probiert. Sollte noch jemand auch die Zeit haben, mir den Fehler zu erklären, gerne :)

Hier das verkürzte Beispielskript tst-identiy:
Bash:
#!/bin/bash

function getorientation () {
    local IFS='='
    local orientation
    echo "getorientation of" "$1"
    o=$(identify -format "%[EXIF:*]" $1 | grep Orientation)
    for i in $o; do
        orientation=$i;
        echo $1: $orientation;
    done
}

while (( "$#" ))
do
    echo processing $1
    orientation=$(getorientation \"$1\")
    echo 'orientation:' $orientation
    shift
done

Beim Aufruf mit
Code:
tst-identify "eins zwei.jpg"

erhalte ich
Code:
processing eins zwei.jpg
identify: unable to open image '"eins': Datei oder Verzeichnis nicht gefunden @ error/blob.c/OpenBlob/3536.
identify: no decode delegate for this image format `' @ error/constitute.c/ReadImage/572.
orientation: getorientation of "eins
 

framp

Moderator
Teammitglied
Nutze z.B. ein Array. Und nutze immer " um Variablen und Funktionsaufrufe.

Bash:
#!/bin/bash

function getorientation () {
    echo "getorientation of" "$1"
}

a=("$@")

for f in "${a[@]}"
do
    echo processing "$f"
    orientation="$(getorientation "$f")"
    echo 'orientation:' "$orientation"
done

EDIT: Es geht noch einfacher ohne Array:

Bash:
#!/bin/bash

function getorientation () {
    echo "getorientation of" "$1"
}

for f in "$@"
do
    echo processing "$f"
    orientation="$(getorientation "$f")"
    echo 'orientation:' "$orientation"
done
 
Zuletzt bearbeitet:

abgdf

Guru
In Perl hätte man all diese sinnlosen bash-Probleme nicht. Einfach die Datenverarbeitung in Perl machen, und Shell-Befehle, die man benötigt, mit "system()", bzw. " `...` " aufrufen (wenn man Output weiterverwenden will). Beispiel: Durchsucht alle ".jpg"-Dateien im aktuellen Verzeichnis auf "exif:Orientation="-Informationen:
Code:
#!/usr/bin/perl

use warnings;
use strict;

sub getOrientation {
    my $fname = shift;
    my @o2;
    my $e = "identify -format \"%[EXIF:*]\" $fname 2>/dev/null";
    my $o = `$e`;
    my $ostring = "Orientation";
    my $exifstring = "exif\:Orientation\=";
    my $i;
    if ($o =~ /$ostring/) {
        @o2 = split(/\n/, $o);
        for $i (@o2) {
            if ($i =~ /^$exifstring/) {
                return substr($i, length($exifstring));
            }
        }
    }
    return "None";
}

my @jpgs = <*jpg>;
my $i;
my $o;
for $i (@jpgs) {
    $o = getOrientation($i);
    if ($o ne "None") {
        print "$i: Orientation: $o\n";
    }
}
 

framp

Moderator
Teammitglied
Der TE hat nur einfach eine goldene Regel in bash nicht beachtet: Alle Variablen und Funktionsaufrufe in Gänsefüßchen zu setzen. Dann hätte er das sinnlose Problem nicht gehabt. Und ich denke wenn er Perl könnte hätte er auch nicht bash genommen denn Perl ist wesentlich mächtiger als bash.
 
OP
G

gorgonz

Hacker
Vielen, vielen Dank für diese Unterstützung :) , das hat mir richtig geholfen!
Das mit pearl ist tatsächlich so, dass ich von dessen Stärke weiß (gerade mit regex), aber nie richtig eingestiegen bin ;-)
 

framp

Moderator
Teammitglied
In bash kannst Du auch sehr gut mit Regex arbeiten. Es gibt da den =~ Operator. Oder man nutzt grep oder andere Tools. Aber ich stimme Dir zu dass das Regexprocessing wesentlich besser in Perl integriert ist.

Anyhow - Hauptsache Du hast eine Loesung in bash oder Perl :)
 

spoensche

Moderator
Teammitglied
Der TE hat nur einfach eine goldene Regel in bash nicht beachtet: Alle Variablen und Funktionsaufrufe in Gänsefüßchen zu setzen. Dann hätte er das sinnlose Problem nicht gehabt. Und ich denke wenn er Perl könnte hätte er auch nicht bash genommen denn Perl ist wesentlich mächtiger als bash.
Das ist so nicht korrekt. Variablen werden via Gänsefüßchen interpoliert. Funktionsaufrufe hingegen nicht.

Code:
orientation=$(getorientation "$f")

Die Ausgabe via
Code:
echo 'orientation: ' "$orientation"

ist ein Coding- Style, den ich gerade zum ersten Mal sehe.

Üblich ist
Code:
echo "orientation: $orientation"
 

abgdf

Guru
Nach dem bißchen, das ich über bash weiß, wäre dann dieses richtiger?
Code:
#!/bin/bash
function getorientation () {
    echo "getorientation of $1"
    o=$(...)
    return "$o"
}
for f in "$@"
do
    echo "processing $f"
    getorientation "$f"
    orientation="$?"
    echo "orientation $orientation"
done
Der return-Wert der Funktion ist also nach Aufruf der Funktion in der automatischen Variable "$?" verfügbar.

Wie gesagt, ich finde das häufig schwer zu durchschauen, z.B. wenn da plötzlich Subshells aufgehen und dadurch Variablenwerte plötzlich "verschwinden", das heißt in der jeweiligen Subshell auf einmal nicht mehr da sind. Und es kam bei mir sogar schon vor, daß ich selbst mit dem umfassendsten Dokument zu bash das Verhalten meines Skripts nicht erklären konnte.
Sobald die Datenverarbeitung bei Arbeit mit Dateien etwas komplexer wird, setze ich daher Perl ein. Da habe ich solche Probleme nie, da läuft das alles immer genau so, wie ich es erwarte: Mit Dateinamen mit Leerzeichen, mit Arrays, mit Funktionen.

robi hat mal geschrieben, der Perl-Interpreter sei ihm zu groß, der verbrauche ihm zu viel Ressourcen (awk sei kleiner). Ich sehe darin heute überhaupt kein Problem. Ich kann locker 10 Instanzen des Perl-Interpreters öffnen, ohne daß sich das im System irgendwie bemerkbar machen würde. Das verträgt das locker. Ich sehe daher eigentlich keinen Grund, mich noch mit den Eigenheiten von bash (oder awk) herumzuschlagen.
Klar, man sollte auch bash kennen, wegen Installationsskripten, die andere geschrieben haben. Aber wie gesagt, niemand zwingt mich, es selbst zu verwenden.
 

framp

Moderator
Teammitglied
Das ist so nicht korrekt. Variablen werden via Gänsefüßchen interpoliert. Funktionsaufrufe hingegen nicht.
Interessant. Das wusste ich bislang nicht. Allerdings bin ich ein Freund von einfachen Regeln. Und in diesem Falle heisst die Regel einfach alles Quoten. Die Quotes beim Function Aufruf aendern das Ergebnis ja nicht.
ist ein Coding- Style, den ich gerade zum ersten Mal sehe.
Ich nutze auch Deine Form. Aber ich habe mich nur um das Problem vom TE gekuemmert (keine Quotes) und an den entsprechenden Stellen seinen Code geaendet.
Ich kann gar nicht hinsehen, so schrecklich ist das ...
Wie wuerdest Du es machen?
Der return-Wert der Funktion ist also nach Aufruf der Funktion in der automatischen Variable "$?" verfügbar.
Leider nicht. Per return kannst Du nur Integer Werte zurueckgeben aber keine Strings.

Es gibt noch eine weiter Form Parameter aus functions als String zurueckzugeben. Ich finde es entspricht in etwa der Pointernutzung in C um Ergebnisse zurueckzuliefern. Ich finde diese Ref Syntax in bash mit der Option -n irgendwie nicht intuitiv und bevorzuge deshalb die Methode die der TE auch nutzt.

Bash:
#!/bin/bash

S="eins zwei"

function echoParm() {
    echo "$@"
}

function pointerParm() {
    declare -n ref=$1
    shift
    ref="$@"
}

echoParmResult=$(echoParm "$S")
echo "echoParmResult unquoted: $echoParmResult"
echoParmResult="$(echoParm "$S")"
echo "echoParmResult quoted: $echoParmResult"

pointerParm pointerParmResult "$S"
echo "pointerParmResult: $pointerParmResult"
 
Der return-Wert der Funktion ist also nach Aufruf der Funktion in der automatischen Variable "$?" verfügbar.
Ja ... aber
Die Funktion in bash sieht in C folgend aus:
Code:
int my_function(const void **p, ..);
Wie du sehen kannst, ist return "string" nicht vorgesehen, so wie generell alle Applikationen unter Linux das nicht vorsehen.
Danach richtet sich auch die Bash.
Man kann mit Tricks arbeiten, dieses (const void **p) läßt ja einiges zu, der Aufwand im Hintergrund das zu Interpretieren ist aber sehr groß.
@framp stellt oben so eine Möglichkeit mit "declare" vor. Dieses Verfahren vermeidet die subshell.
Beispiel: Lesen und Schreiben einer Referenz und der Aufwand dahinter
Code:
set_ref_string()
{
    local str="${!1}"    # aus einer Liste von Variablen den Namen finden, den Pointer referenzieren, den Inhalt von $VAR in $str lesen
    declare -n ref="$1"    # nocheinmal aus der Liste --"--, den Pointer von $VAR auf *ref setzen
    ref="$str 222"        # alten Wert von $VAR + " 222" über Pointer *ref in $VAR schreiben.
    return 0         # return integer. z.B. 0 für OK, andere Datentype funktioniert nicht
}
VAR="111"
set_ref_string VAR        # den Namen der Variable übergeben als Referenz
echo "$VAR"            # "111 222"
Schaut schon recht gut aus, der Aufwand ist aber enorm. Deshalb ist eine globale Variable die einfachste und beste Lösung.

Wie gesagt, ich finde das häufig schwer zu durchschauen, z.B. wenn da plötzlich Subshells aufgehen und dadurch Variablenwerte plötzlich "verschwinden", das heißt in der jeweiligen Subshell auf einmal nicht mehr da sind. Und es kam bei mir sogar schon vor, daß ich selbst mit dem umfassendsten Dokument zu bash das Verhalten meines Skripts nicht erklären konnte.
Das wundert mich nicht. Mit Subshells kann man in Bash Classes erstellen, die typischerweise in einem anderen Speichersegment
ablaufen. D.h. so wie in C++ werden Variablen über die Grenze der Class nicht vererbt und der Inhalt ist natürlich weg.
Diese Möglichkeit ist leider nirgendwo beschrieben. Du brauchst das jedoch nicht! Wann immer du in eine solche Situation
kommst, wo in Subshells Variablenwerte verschwinden, hast du unnötigerweise die Funktionalität der Subshell verwendet.
Einzig zum Starten von externen Programmen ist diese Funktion bedeutend, da gibt es aber keine Classes und somit auch nicht das Problem.

robi hat mal geschrieben, der Perl-Interpreter sei ihm zu groß, der verbrauche ihm zu viel Ressourcen (awk sei kleiner). Ich sehe darin heute überhaupt kein Problem. Ich kann locker 10 Instanzen des Perl-Interpreters öffnen, ohne daß sich das im System irgendwie bemerkbar machen würde.
Da muß ich dem @robi Recht geben. Du merkst es nicht, dein Rechner hat genug Resources, aber auf anderen Systemen wie
ARM embedded oder ähnlich ist Perl "mittlerweile" unbrauchbar. Es frisst was es kriegen kann. Ein Grund (wie oben) sind die Möglichkeiten unter Perl des return string, array oder hash. Die müssen allokiert werden und weil niemand wissen kann, wie lange
und wo die noch gebraucht werden, bleiben sie meist als Leichen im Speicher stehen. Sogar die Datatype "my" ist davon betroffen.

Ich sehe daher eigentlich keinen Grund, mich noch mit den Eigenheiten von bash herumzuschlagen.
Klar, man sollte auch bash kennen, wegen ...
Das möchte ich wohl meinen.
Im Vergleich zu Perl ist Bash performanter, braucht nur einen Bruchteil der resources und ist sehr gut verzahnt mit dem OS.
Ich bin sogar der Meinung, dass dir kein script (Werkzeug) unter Perl gelingt, dass nicht nach Bash übersetzt werden könnte.
Möglicherweise könntest du mit sehr umfassendem Regex meine Meinung entkräften, aber wer braucht das schon.

Gruß
Gräfin Klara
 
Zuletzt bearbeitet:

framp

Moderator
Teammitglied
Deshalb ist eine globale Variable die einfachste und beste Lösung.
Seit wann ist die beste Loesung Parameter von und zu Funktionen per globaler Variablen zu uebergeben :oops: Das meinst Du doch nicht wirklich ernst :)
Du brauchst das jedoch nicht! Wann immer du in eine solche Situation
kommst, wo in Subshells Variablenwerte verschwinden, hast du unnötigerweise die Funktionalität der Subshell verwendet.
Diese Aussage von Dir verstehe ich nicht :cry:

Und leider hast Du nich auf meine Frage oben geantwortet wie Du das FunktionsErgebnisUebergabeProblem an den Aufrufer loesen wuerdest.

Ich sehe ein dass das Pattern
Code:
result=$(functionCall parm1 parm2 ...)
nicht das effizienteste ist. Es entspricht aber dem allgemeinen Pattern was in anderen Programmiersprachen bei Funktionen bzw Prozeduren genutzt wird. Aber i.d.R. ist es bei bash Scripts nicht notwendig die letzten Nannosekunden Laufzeit rauszuquetschen.
 
Seit wann ist die beste Loesung Parameter von und zu Funktionen per globaler Variablen zu uebergeben :oops: Das meinst Du doch nicht wirklich ernst :)
Nicht von und zu, sondern von Funktionen wenn Datentype nicht int
Code:
TMP_VAR=""

get_name()
{
    TMP_VAR="${1/@*/}"
    return 0
}
get_host()
{
    TMP_VAR="${1#*@}"
    return 0
}
email="framp@email.com"
get_name "$email"
echo "$TMP_VAR"        # framp
get_host "$email"
echo "$TMP_VAR"        # email.com
exit 0
Einfach und effizient und ja, das meine ich ernst


Diese Aussage von Dir verstehe ich nicht :cry:
Beispiel:
Code:
read_a()
{
    local fname path="$1"
    while read fname; do
        do_anything
    done < <(find "$path" -type f)
}
read_b()
{
    local fname path="$1"
    while read fname; do
        do_anything
    done <<< $(find "$path" -type f)
}
In der Funktion read_a läuft do_anything in einer subshell (anderer Speicherbereich) wie bei C++ andere Class.
Variablen außerhalb der loop werden nicht vererbt, Variablen innerhalb der loop sind außerhalb der loop nicht vorhanden.
Funktion read_b erledigt die selbe Aufgabe ohne subshell, also Variablen sind innerhalb und außerhalb erreichbar.
Deshalb zu seinem Problem mit subshell und "verschwundenen Werten" mein: "Du brauchst das jedoch nicht!".

Und leider hast Du nich auf meine Frage oben geantwortet wie Du das FunktionsErgebnisUebergabeProblem an den Aufrufer loesen wuerdest.
Gar nicht, meine Lösung bräuchte keine Funktion


Ich sehe ein dass das Pattern
Code:
result=$(functionCall parm1 parm2 ...)
nicht das effizienteste ist. Es entspricht aber dem allgemeinen Pattern was in anderen Programmiersprachen bei Funktionen bzw Prozeduren genutzt wird. Aber i.d.R. ist es bei bash Scripts nicht notwendig die letzten Nannosekunden Laufzeit rauszuquetschen.
Natürlich wird es genutzt und deine Lösung funktioniert auch.
Doch wenn ich mir das ansehe, dann sehe ich einen $(functionCall) plus ein echo in der Funktion, was 2 subshells spawned,
die je über eine eigene pipe kommunizieren müssen, erst child <> child dann auch noch child <> parent für nur ein Resultat.
Fast wie München nach Nürnberg über Shanghai. Ich als alte C Programmiererin ertrage das nicht!
Ich bin pizlig, ein Performancefreak, mein Fehler, tut mir leid.

Gruß
Gräfin Klara
 
Zuletzt bearbeitet:

abgdf

Guru
Die Funktion in bash sieht in C folgend aus:
Code:
int my_function(const void **p, ..);
Wie du sehen kannst, ist return "string" nicht vorgesehen, so wie generell alle Applikationen unter Linux das nicht vorsehen.
Danach richtet sich auch die Bash.
Das ist doch ein gutes Beispiel. Funktionen soll man also in bash außer in sehr einfachen Fällen eigentlich gar nicht benutzen, sonst hätte man in den Source-Code von bash reingeschrieben, daß z.B. ein Array zurückgegeben wird (wie eben in Perl).
Das ist doch sehr beschränkt - für mich zu beschränkt, um komfortabel zu sein.

Im konkreten Thema hier wird mit "orientation" aber soweit ich sehe gerade ein "int" zurückgegeben. Daher hatte ich das mit dem "return" vorgeschlagen.
 

framp

Moderator
Teammitglied
Im konkreten Thema hier wird mit "orientation" aber soweit ich sehe gerade ein "int" zurückgegeben.
Gemaess Interface ist es ein int der zurueckgegeben wird. Aber es koennen nur Werte zwischen 0 und 255 zurueckgegeben werden. Also nur ein byte - kein int :oops:
Das ist doch sehr beschränkt - für mich zu beschränkt, um komfortabel zu sein.
Naja - man kann ueber die schon oben beschriebenen Workarounds leben. Aber ich stimme Dir zu dass die functions in bash schon besser haetten implementiert werden koennen (wenigstens den vollen int Wertebereich als Rueckgabe - auch negative Werte) und Prozeduren die beliebige Werte zurueckgeben koennen fehlen ganz. Deshalb ist man in bash gezwungen die oben beschriebenen Umwege zu gehen fuer Code Decomposition. Keine Ahnung warum man das damals so entschieden hat.
 

framp

Moderator
Teammitglied
Einfach und effizient und ja, das meine ich ernst
Danke fuer die Code Snippets. Das ist natuerlich aus Laufzeitgesichtspunkten die effizienteste Methode. Wie ist es bei tieferer Schachtelung von functions? Dann nutzt Du pro function immer eine neue globale Variable fuer die Function? Vermutlich dann per einer Naming convention - z.B. heisst die globale Variable x_return fuer eine function x? Rekursion geht dann aber leider nicht :cry:
Deshalb zu seinem Problem mit subshell und "verschwundenen Werten" mein: "Du brauchst das jedoch nicht!".
Verstanden. Danke.
Fast wie München nach Nürnberg über Shanghai. Ich als alte C Programmiererin ertrage das nicht!
Ich bin pizlig, ein Performancefreak, mein Fehler, tut mir leid.
:D Ich kannte den Terminus pizlig bislang nicht und musste ihn erst nachsehen. Aber deshalb musst Du Dich nicht entschuldigen. Auch ich habe Dinge wo sich bei mir die Nackenhaare hochstellen.

Ja, es ist aus dieser Sicht natuerlich umstaendlich. Aber es folgt - wenn auch etwas umstaendlich - der allgemeinen Code Decomposition Syntax und Semantik die es in fast allen anderen Programmiersprachen gibt. Ich glaube nicht dass Du in C globale Variablen nutzt um Werte einer Prozedur an den Caller zurueckzugeben :)

PS: Deine beiden Beispiele read_a und read_b nutzt schon advanced bash Syntax und sind nicht einfach zu verstehen. Ich weiss nicht ob der TE die anwenden will. Die von "Muenchen ueber Nuernberg nach Shanghai" Methode ist leichter zu verstehen und anzuwenden - die Syntax kennt jeder :) Es fehlten eben nur die Quotes in seinem Code.
 
Danke fuer die Code Snippets. Das ist natuerlich aus Laufzeitgesichtspunkten die effizienteste Methode. Wie ist es bei tieferer Schachtelung von functions? Dann nutzt Du pro function immer eine neue globale Variable fuer die Function?
Wenn es mehrer Funktionen braucht, die nested sind, dann mach ich das folgend
Beispiel:
Code:
set_sub()
{
    local -n ref="$1"      # get reference to tmp
    ref="set_sub"       # string to variable tmp
    return 0
}
set_ref()
{
    local -n ref="$1"         # Referenz auf Variable VAR
    local tmp text="$2" num=$3
    set_sub "tmp"         # Name der lokalen Variable tmp
    ref="set_ref $text $num $tmp"      # string per Referenz nach VAR
    return 0
}
VAR="framp"                # default, damit man sieht, dass diese Variable auch wirklich verwendet wird
set_ref "VAR" "plop" 122            # z.B. Aufruf mit: Variablen Name, irgendein String, irgendeine Nummer
echo "$VAR"                      # "set_ref plop 122 set_sub"
exit 0
Damit lassen sich Funktionen beliebig Verschachteln und aus jeder Funktion ein oder mehrere strings oder arrays zurückführen.
Im Grunde ist es ähnlich wie dein obiger Vorschlag mit declare (Referenz auf Variable),
hier verwende ich aber eine Bash interne Funktionalität, die den Aufruf des binaries declare nicht benötigt.
Mit der Performance und Lesbarkeit bin ich sehr zufrieden.

Gruß
 
Zuletzt bearbeitet:
Das ist doch sehr beschränkt - für mich zu beschränkt, um komfortabel zu sein.
Das lasse ich so nicht gelten und es läßt sich auch einfach überprüfen.
Schreib du ein script in Perl, darf gerne kompliziert sein, ich übersetze es in Bash.
Dann vergleichen wir Funktionalität und Performance.
Das könnte deine Meinung ändern

Gruß
Gräfin Klara
 

framp

Moderator
Teammitglied
Wenn es mehrer Funktionen braucht, die nested sind, dann mach ich das folgend
Vielen Dank fuer das Beispiel (y) Aus dem ist auch zu sehen dass mit Deiner Methode auch rekursive Aufrufe funktionieren.
Die Methode mit den Globals gefaellt mir nicht. Aber die andere Methode mit der Variablenreferenz finde ich durchaus interessant. Ich werde Deine Methode bei meinen naechsten Scripts mal praktisch nutzen und sehen wie sie mir so im Einsatz gefaellt. Vielleicht hast Du mich ueberzeugt :)

Fuer Einsteiger in bash Scripts die Programmiererfahrung haben ist aber result=$(func parm1 parm2) sicherlich die verstaendlichere Methode des Aufrufs und der Ergebnislieferung.
 

abgdf

Guru
Das lasse ich so nicht gelten und es läßt sich auch einfach überprüfen.
Schreib du ein script in Perl, darf gerne kompliziert sein, ich übersetze es in Bash.
Dann vergleichen wir Funktionalität und Performance.
Das könnte deine Meinung ändern
Die Frage ist eher, wie einfach es ist, und wie schnell es geht, in der jeweiligen Sprache sein Ziel zu erreichen. Wenn man an das Verhalten von bash, an das Prinzip der Pipes und die Vielzahl der Optionen der Shell-Befehle gewöhnt ist, mag es sein, daß man auch damit einiges erreichen kann.
Um in das Schreiben von Skripten einzusteigen, halte ich bash (die Shell) wegen ihrer Eigenwilligkeit aber gar nicht für geeignet. Es ist eigentlich schade, daß Viele versuchen, diesen Weg zu gehen, und dann dabei oft steckenbleiben und nicht weiterkommen.
Es sind seit vielen Jahren auch immer dieselben wenigen User, die versuchen, Antworten auf Skripting-Fragen zu geben. Es hätten eigentlich viel mehr neue Leute dazukommen müssen, wenn der Einstieg in das Skripting als einfach und leicht empfunden würde. Was er meiner Meinung nach sein kann, nur halt nicht gerade in bash.
 
Zuletzt bearbeitet:
Oben