• 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] Imagemagick - inputfiles mit Leerzeichen

f.gruber

Hacker
Hallo,
wie kann ich an den ImageMagick-Befehl montage Dateinamen übergeben, welche Leerzeichen enthalten? Ich plage mich nun schon zwei Tage und trotz intensiver Internetsuche kann ich das Problem nicht lösen.

Hier mein Versuch:
Code:
if [ "$#" -eq 0  ]  ; then
  sourcefiles="*.[jJ][pP]*[gG]"
else
  sourcefiles=$*
fi

targetfile=/tmp/indexprint.pdf
rm $targetfile &> /dev/null

montage -monitor -auto-orient -label %f -geometry 320x320+10+10 -tile 3x4 $sourcefiles $targetfile

Im Verzeichnis sind folgende drei Dateien:
Code:
ls
Bild_65.jpg  Bild 71.jpg  Bild_83.jpg

Bei Aufruf des Skriptes ohne Parameter werden alle drei Dateien verarbeitet (auch jene mit dem Lerrzeichen)
Wenn ich das Programm aber mit den 3 Dateinamen als Parameter aufrufe, dann scheitert der Vorgang an der Datei mit dem Leerzeichen:

Code:
indexprint.sh "Bild_65.jpg"  "Bild 71.jpg"  "Bild_83.jpg"

Wo liegt mein Fehler?
 
Hallo f.gruber,

f.gruber schrieb:
Code:
if [ "$#" -eq 0  ]  ; then
  sourcefiles="*.[jJ][pP]*[gG]"
else
  sourcefiles=$*
fi

Und was hältst Du davon, wenn Du in Deine if-Schleife einen Teil einbaust der die Leerzeichen aus den Dateinamen entfernt, etwa so?:
Code:
for i in *.jpg *.JPG; do mv "$i" "$(echo $i | sed 's/ /_/g')"; done

Lieben Gruß aus Hessen
 
A

Anonymous

Gast
Das ist nicht dein Fehler, das ist eine Eigenheit der Bildnamesübergabe die auf ImageMagick zurückzuführen ist.
ImageMagick expandiert hier intern noch einmal die Dateinamen im Filesystem. Alle normalen Versuche die man auf der Shell normalerweise machen würde und das Leerzeichen mit einem "\" zu maskieren schlagen dabei fehl.

eine ziemlich dreckige Möglichkeit wäre
Code:
sourcefiles="${@/ /?}"
hier wird das Leerzeichen zu einem "?" das funktioniert aber nur solange wie du nicht zB. Dateien
Bild 71.jpg Bild-71.jpg Bild_71.jpg usw. hättest. Anstatt der Datei mit dem Leerzeichen würden wohl dann alle 3 eingefügt.

die 2. Möglichkeit ist sauber bedeutet aber viel mehr Aufwand im Script.
du bräuchstest für beide Fälle eine eigene montage Ausführungszeile.
einmal wenn du ohne Optionen aus dem Verzeichis lesen lassen willst wie bisher auch.
Code:
montage -monitor -auto-orient -label %f -geometry 320x320+10+10 -tile 3x4 $sourcefiles $targetfile
und einmal für den Fall das du die Dateien per Kommandozeile übergeben willst.
Code:
montage -monitor -auto-orient -label %f -geometry 320x320+10+10 -tile 3x4 "$@" $targetfile

Wenn beide Methoden für dich nichts sind, dann als nächstes mal bei ImageMagick nachlesen, irgendwo war dort mal was beschrieben wie man mit solchen Problemen umgehen kann.

Letzte Methode Script komplett umbauen. Wie eine funktionierende Lösung aussehen könnte, müsste ich aber auch erst ausprobieren.

robi
 

RME

Advanced Hacker
Hallo,

In Deinem Script sieht "montage" als Input
Code:
Bild_65.jpg Bild 71.jpg Bild_83.jpg
"montage" kann da nicht entscheiden wo ein Dateiname beginnt und endet.

Eine Möglichkeit wäre:
Code:
#!/bin/bash
#

targetfile=/tmp/indexprint.pdf
rm $targetfile &> /dev/null

IFS=$(echo -en "\n\b")

if [ "$#" -eq 0  ]  ; then
  sourcefiles="*.[jJ][pP]*[gG]"
  montage -monitor -auto-orient -label %f -geometry 320x320+10+10 -tile 3x4 $sourcefiles $targetfile
else
  montage -monitor -auto-orient -label %f -geometry 320x320+10+10 -tile 3x4 $* $targetfile
fi

# --end of script--
$IFS (= Internal Field Separator) ist per Default = 'space'

Gruss,
Roland
 
OP
F

f.gruber

Hacker
Herz-von-Hessen schrieb:
...
Und was hältst Du davon, wenn Du in Deine if-Schleife einen Teil einbaust, der die Leerzeichen aus den Dateinamen entfernt, etwa so?:
Code:
for i in *.jpg *.JPG; do mv "$i" "$(echo $i | sed 's/ /_/g')"; done
... dann müsste ich ja die Dateien umbenennen. Da es nicht meine sind, mache ich das lieber nicht.
Natürlich könnte ich die dem Script übergebenen Dateien vor der Bearbeitung durch ImageMagick nach /tmp kopieren und dort umbenennen. Das Kopieren dauert aber zu lang bei vielen Dateien. Das ergibt keine gute Performance für mein Skript, welches ich in das Service Menü vom Dolphin einbauen möchte.
 
OP
F

f.gruber

Hacker
robi schrieb:
Code:
montage -monitor -auto-orient -label %f -geometry 320x320+10+10 -tile 3x4 "$@" $targetfile
Ok, danke. So funktioniert es mit Leerzeichen im Dateinamen.
Wenn ich aber z.B. folgendes mache, geht es wieder nicht. Warum?
Code:
sourcefiles=$@
montage -auto-orient -label %f -geometry $geometry -tile $tile -pointsize $fontsize "$sourcefiles" $targetfile

Auch das geht nicht.
Code:
montage -monitor -auto-orient -label %f -geometry 320x320+10+10 -tile 3x4 "$*" $targetfile
Wo liegt eigentlich intern der Unterschied zwischen
Code:
$@
und
Code:
$*
?
 
A

Anonymous

Gast
f.gruber schrieb:
Wo liegt eigentlich intern der Unterschied zwischen
Code:
$@
und
Code:
$*
?
$* und $@ ist gleich, (Unterschiede könnten eventuell aber auftreten wenn bestimmte Schalter oder interne Variablen der Basheigenschaften vorher manuell anders gesetzt werden.)
aber "$*" und "$@" ist was ganz anderes, das ist eine ziemlich lange und im Detail nicht einfache Geschichte, ;)
hier nur die Kurzfassung aus der Manpage der bash
Code:
Special Parameters
       The shell treats several parameters specially.  These parameters may only be referenced; assignment to them is not allowed.
       *      Expands to the positional parameters, starting from one.  When the expansion occurs within double quotes, it expands 
              to a single word  with  the  value  of  each parameter  separated by the first character of the IFS special variable. 
              That is, "$*" is equivalent  to "$1c$2c...", where c is the first character of the value of the IFS variable.  If IFS is unset, 
              the parameters are separated by spaces. If IFS is null, the parameters are joined without intervening separators.
       @    Expands to the positional parameters, starting from one.  When the expansion occurs within double quotes, each
              parameter expands to a separate  word.   That  is, "$@"  is equivalent to "$1" "$2" ...  If the double-quoted expansion
              occurs within a word, the expansion of the first parameter is joined with the beginning part of the original word, and 
              the expansion of the last parameter is joined with the last part of the original word.  When there are no positional  
              parameters,  "$@" and $@ expand to nothing (i.e., they are removed).
und der Inhalt von "$@" lässt sich auch nicht 1 zu 1 ohne zusätzlichen ziemlich komplizierten Zusatzkram an eine andere Variable übergeben.

robi
 
Hallo f.gruber,

f.gruber schrieb:
... dann müsste ich ja die Dateien umbenennen. Da es nicht meine sind, mache ich das lieber nicht.
O.K. das wusste ich ja nicht.
Generell gibt es mit Leerzeichen in Scripten öfter Probleme so das man besser darauf verzichtet diese in Dateinamen zu benutzen.

f.gruber schrieb:
Natürlich könnte ich die dem Script übergebenen Dateien vor der Bearbeitung durch ImageMagick nach /tmp kopieren und dort umbenennen
Um das zu beschleunigen könnte man ein /tmp Verzeichnis im RAM anlegen, so als "Notlösung"

Lieben Gruß aus Hessen
 
A

Anonymous

Gast
abgdf schrieb:
Mit einiger Wahrscheinlichkeit dürften die Perl-Bindings für Imagemagick (= PerlMagick) das Problem gar nicht haben ...
Das scheint wohl wahr zu sein, doch leider muss man wohl bei einem Objekt vom Typ Image::Magick jedes Bild einzeln mit Read anhängen und damit schön in einer foreach Schleife rumeiern. Man hat also wohl dort gar nicht die Möglichkeit alle Dateien von der Befehlszeile mit einem Ritt zu bearbeiten. Damit kommt man schon von Anfang an gar nicht in diese Leerzeichen Problematik .

vereinfacht in den Optionen für das zusammengesetzte PDF wohl in etwa so hier:
Code:
#!/usr/bin/perl

use Image::Magick;

$images = Image::Magick->new();
$images->Montage(geometry=>'320x320');

foreach $file (@ARGV) {
     $images->Read(filename => $file) &&
        die "Read failed";
 }
$images->Write(filename=>'image.pdf')

robi
 
A

Anonymous

Gast
Spielwurm schrieb:
zum ertsen Post: probiers mal mit:
Code:
sourcefiles="$*"
geht nicht, bzw. kann gar nicht gehen wenn Leerzeichen im Dateinamen sind, glaubt mir nur auch mal was, manchmal hab ich wirklich Recht.. ;)

robi
 

Spielwurm

Advanced Hacker
Ich glaub Dir ja gern, aber bei meinen paar Scripten, mit denen ich Videos umwandle, funktioniert das Verarbeiten von Dateien mit Leerzeichen im Namen nur, wenn ich die $-Variablen in Anführungszeichen setze ...
 
OP
F

f.gruber

Hacker
robi schrieb:
Code:
#!/usr/bin/perl
use Image::Magick;

$images = Image::Magick->new();
$images->Montage(geometry=>'320x320');

foreach $file (@ARGV) {
     $images->Read(filename => $file) &&
        die "Read failed";
 }
$images->Write(filename=>'image.pdf')

robi

Wenn ich das probiere, bekomme ich gleich in Zeile 2 die Fehlermeldung:
Code:
... use: Kommando nicht gefunden.
Habe von perl leider keine Ahnung.
 
A

Anonymous

Gast
Wenn ich das probiere, bekomme ich gleich in Zeile 2 die Fehlermeldung:
Code:
... use: Kommando nicht gefunden.

Habe von perl leider keine Ahnung.
wahrscheinlich versuchts du das wie folgt zu starten,
Code:
sh script .........
das kann nicht gehen, das ist kein Shellscript sondern ein Perlscript, es müssen also Ausführungsrechte auf die Datei und das dann Starten wie ein normales Programm.
Code:
./script .........
oder
mit
Code:
perl script..........
Außerdem muss noch das Paket "perl-PerlMagick" installiert sein, sonst geht es auch nicht, sollte aber eine andere Fehlermeldung bringen.



Spielwurm schrieb:
aber bei meinen paar Scripten, mit denen ich Videos umwandle, funktioniert das Verarbeiten von Dateien mit Leerzeichen im Namen nur, wenn ich die $-Variablen in Anführungszeichen setze ...

stell dir mal vor du willst einem Script mehrere Bilder übergeben: Aufruf soll so aussehen
Code:
-/script.sh datei_1.jpg datei-2.jpg "datei 3.jpg" /home/user/bilder/img1*.jpg "/home/tmp/img *.jpg"
diese Argumente kannst du nicht einfach aus $* pder $@ holen und in eine Variable schreiben und dann irgendwo durch das Auslesen dieser Variable wieder richtig in einen Befehl einsetzen. Der Inhalt den du aus der Variable herausholst um ihn mit einem Schlag richtig verarbeiten zu können muss dann so aussehen.
Code:
datei_1.jpg datei-2.jpg "datei 3.jpg" /home/user/bilder/img1abc.jpg /home/user/bilder/img1dcf.jpg "/home/tmp/img 777.jpg" "/home/tmp/img 888.jpg"
das ist genau das was du aus "$@" rausholen würdest. Nur leider würden die " beim Ablegen dieses Inhaltes in eine Variable verloren gehen und dann wenn du diese Variable wieder in einen Befehl einsetzen willst, fehlen.



robi
 

RME

Advanced Hacker
Hallo,
robi schrieb:
Spielwurm schrieb:
zum ertsen Post: probiers mal mit:
Code:
sourcefiles="$*"
geht nicht, bzw. kann gar nicht gehen wenn Leerzeichen im Dateinamen sind, glaubt mir nur auch mal was, manchmal hab ich wirklich Recht.. ;)
RME schrieb:
$IFS (= Internal Field Separator) ist per Default = 'space'
also:
Code:
cat testscript
#!/bin/bash

IFS=$(echo -en "\n\b")

sourcefiles=$*

echo "$sourcefiles"

# --end of script--
Code:
./testscript 1_aaa.jpg "2_b b.jpeg" "3_c c c.jpeg"
1_aaa.jpg
2_b b.jpeg
3_c c c.jpeg
Gruss,
Roland
 
A

Anonymous

Gast
RME schrieb:
IFS=$(echo -en "\n\b")
gute Idee, beseitigt das Problem scheinbar auf einen Hieb komplett.

Vorsicht: wenn im Script auch read verwendet wird, muss IFS eventuell vorher wieder umgesetzt werden.
Ebenso sind auch verschiedene Array Funktionen betroffen. (dürfte Otto-Normal-Scripter aber weniger treffen)

robi
 
OP
F

f.gruber

Hacker
robi schrieb:
RME schrieb:
IFS=$(echo -en "\n\b")
gute Idee, beseitigt das Problem scheinbar auf einen Hieb komplett.
Ja, das ist wirklich die Lösung.

Habe nun das Skript fertig. Das Skript soll einen Indexprint von Fotos erzeugen.
Es werden immer nur so viele Bilder an montage übergeben als auf eine Seite passen. Die Anzahl der Bilder pro Seite ergibt sich aus dem per kdialog vom Benutzer gewählten Wert (2x3, 3x4, 4x5, ...)

Das Skript verarbeitet wenn es auf der Konsole ohne Eingabeparameter aufgerufen wird, die Dateien des aktuellen Verzeichnisses.
Ich rufe das Skript aber auch über das Kontextmenü von Dolphin auf. Dolphin übergibt interessanterweise den Verzeichnisnamen, wenn keine Datei ausgewählt ist :???: - oder eben eine Liste der markierten Dateien.
Der Aufruf erfolgt in einer desktop Datei im Verzeichnis:
Code:
~/.kde4/share/kde4/services/ServiceMenus/
mit folgendem Befehl:
Code:
konsole --nofork --geometry 500x200+200+200  -e bash /usr/local/bin/indexprint.sh %U
Damit ich im Skript die Dateinamen in einheitlicher Form weiterverarbeiten kann, schreibe ich die Namen in eine temporäre Datei. Mit Hilfe von sed habe ich dann die Paginierung hingekriegt.

Hier ist das ganze Skript. Vielleicht will es noch jemand verbessern :roll:
Code:
#!/bin/bash
file_list=/tmp/file_list.txt
rm $file_list &> /dev/null

tile=$(kdialog --radiolist "Anordnung der Bilder:" 2x3 "2 x 3" off 3x4 "3 x 4" on 4x5 "4 x 5" off)
case $tile in
  2x3) geometry=800x800
    margin=50
    fontsize=25
    fotos_per_page=6
    ;;
  3x4) geometry=640x640
    margin=35
    fontsize=25
    fotos_per_page=12
    ;;
  4x5) geometry=400x400
    margin=25
    fontsize=20
    fotos_per_page=20
    ;;
esac

geometry=${geometry}+${margin}+${margin}
IFS_bak=$IFS
IFS=$(echo -en "\n\b")

if [ $# -eq 0  ]  ; then
  # Skript wurde ohne Parameter aufgerufen
  find . -maxdepth 1 -name "*.[jJ][pP]*[gG]" > $file_list
else   
  # ... mit Parameter(n)
  if [ -d "$*" ] ; then 
    # Parameter ist ein Verzeichnis
    find "$*/" -maxdepth 1 -name "*.[jJ][pP]*[gG]" > $file_list
  else		    
    # eine oder mehrere Dateien als Parameter
    for file in $@ ; do
      echo $file >> $file_list
    done
  fi
fi

for ((counter=0; counter < `cat $file_list | wc -l` ; counter++)); do 
  if [ `expr $counter % $fotos_per_page` -eq 0 ] ; then
    # Seitenaufteilung durchführen
    page_number=`expr $counter / $fotos_per_page + 1`
    first_pic=`expr $counter + 1`
    last_pic=`expr $counter + $fotos_per_page`
    sourcefiles=`sed -n "${first_pic},${last_pic}p" $file_list`
    
    indexprint=/tmp/indexprint_$page_number.jpg
    rm $indexprint &> /dev/null
           
    montage -monitor -auto-orient -label %f \
      -geometry $geometry -tile $tile -pointsize $fontsize $sourcefiles $indexprint
    targetfiles="$targetfiles $indexprint" 
      
    # Rand hinzufügen
    pagemargin=`expr $margin \* 2`
    mogrify -border $pagemargin -bordercolor \#FFFFFF $indexprint
  fi
done

IFS=$IFS_bak
okular $targetfiles
 
Oben