staging.inyokaproject.org

Durch mehere Ordner gehen und Befehl ausführen:

Status: Ungelöst | Ubuntu-Version: Ubuntu 16.04 (Xenial Xerus)
Antworten |

Armani42

Anmeldungsdatum:
28. Juni 2015

Beiträge: 7

Hallo,

ich möchte gerne in einem Shell Skript durch mehrere Ordner gehen, einen Befehl ausführen und wieder zurück gehen etc.

Folgendes habe ich bis jetzt geschrieben:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
for i in $( ls ); do
    cd $i
    echo $i
        for j in $( ls ); do
                cd $j
                echo $j 
                for file in {1}; do
                        if [ "${file}" != "*.out" ]
                        then
                            qsub *.job

                        fi
                done
                cd /work/tmongelli/CdSe/Cd2Se2/cation
       done
done

Folgende Situation:

Ich befinde mich im Ordner "cation" In diesem sind 18 Ordner namens Iso1-18_Cd2Se2

In jedem dieser Iso Ordner ist immer nur ein einziger Ordner.

Also soll das Skript nun folgendes machen:

Es soll in den Ordner "Iso1_Cd2Se2" gehen und dann in den Ordner gehen, welcher sich in "Iso1_Cd2Se2" befindet. Danach soll er schauen, ob eine Datei mit der Endung ".out" existiert. Wenn diese nicht existiert, soll er den Befehl qsub *.job durchführen und danach wieder in den Ordner "cation" wechseln und den Ordner "Iso2_Cd2Se2" betreten und wieder das selbe machen, bis er alle 18 Ordner durch hat.

Leider bekomme ich das nicht so ganz hin. Ich hoffe, jemand kann mir helfen.

Lg

Tobi

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Also ...

  1. Die Ausgabe von ls zu parsen ist Kappes. - Willst Du nicht, und brauchst Du auch gar nicht.
    Die Verzeichnisse kriegst Du viel einfacher direkt mit einer Filename Expansion:

    for i in */ ; do
        # ...
  2. Grundsätzlich sauber "quoten". - gut, Du hast hier keine Verzeichnisnamen mit Leerzeichen drin, aber wehe, es kommt mal so einer dazwischen !

  3. Verstehe ich das richtig, dass Deine Verzeichnisstruktur ± so aussieht ? :

    caption
    |-- Iso1_Cd2Se2
    |   `-- Iso1_Cd2Se2
    |       `-- dateix.out
    | ...
    |
    `-- Iso1_Cd3Se6
        `-- Iso1_Cd3Se6
            `-- dateiy.out

    ... und Du jetzt dort einfach nach den *.out-Dateien suchen willst ? - das ginge nämlich durchaus direkt:

    1
    2
    3
    4
    5
    6
    cd  caption
    for d in */ ; do
        if [ ! -f "$d$d"*.out ]; then
             echo "$d$d"*.out fehlt.
        fi
    done
    

Das mit dem qsub - Befehl habe ich noch nicht ganz verstanden, aber das kannst Du ja nochmal genauer erklären.

LG,

track

Armani42

(Themenstarter)

Anmeldungsdatum:
28. Juni 2015

Beiträge: 7

Hi, vielen Dank erstmal ☺

Ja also meine Ordnerstruktur ist ähnlich.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
caption

-- Iso1_Cd2Se2
     -- Iso1_Cd2Se2_ezdpp
         -- dateiw.out
...

--Iso18_Cd2Se2
     -- Iso18_Cd2Se2_ezdpp
         -- dateix.out

Also sprich die Datei im Ordner hat nochmal einen etwas längeren Namen :/ Kann man da dein Skript ein wenig umschreiben? Oder kann man evtl sowas wie in Python machen? "$d$d + "_ezdpp"?

Ach ja und qsub ist einfach nur ein Befehl um ein Skript auf einem Cluster zu submitten 😉

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Das cd in das Wurzelverzeichnis Deines Datenbäumchens mache ich nur, damit ich mit der Filename Expansion direkt den nackten Verzeichnisnamen bekomme.
Mehr Zauberei steckt nicht dahinter.

Dann sehe ich, dass bei Dir die Unter-Unterverzeichnisse noch ein Anhängsel haben. Das muss man beim Bilden der Pfadnamen natürlich berücksichtigen, sonst findet er nix. Probier mal, ob er damit alle vorhandenen *.out- Dateien findet:

1
2
3
4
5
6
cd  caption
for d in */ ; do
    if [ -f "$d${d%/}"*/*.out ]; then
         echo "$d${d%/}"*/*.out
    fi
done

Danach kannst Du die Abfrage auf die Existenz der "datei*.out" ja wieder invertieren.

Zum Verständnis kannst Du ja mal die Geschichte mit den Parameter Expansions nachlesen,
und überhaupt das ganze Kapitel im Bash Manual dazu.
Und mal selber testen:

1
2
3
4
5
6
7
8
cd  caption
for d in */ ; do
    echo "$d"
    echo "${d%/}"
    echo "$d${d%/}"*
    echo "$d${d%/}"*/*.out
    exit
done

Dann wirst Du ziemlich schnell begreifen, wie der Hase läuft.

LG,

track

Armani42

(Themenstarter)

Anmeldungsdatum:
28. Juni 2015

Beiträge: 7

Danke, das hilft mir echt weiter, super 😀

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

track schrieb:

Dann sehe ich, dass bei Dir die Unter-Unterverzeichnisse noch ein Anhängsel haben. Das muss man beim Bilden der Pfadnamen natürlich berücksichtigen, sonst findet er nix. Probier mal, ob er damit alle vorhandenen *.out- Dateien findet:

1
2
3
4
5
6
cd  caption
for d in */ ; do
    if [ -f "$d${d%/}"*/*.out ]; then
         echo "$d${d%/}"*/*.out
    fi
done

Das geht aber schief, wenn da zufällig mehrere Dateien liegen, deren Name auf ".out" endet:

1
2
3
4
5
6
7
8
$ find
.
./foo
./foo/abc.out
./foo/bar.out
$ for d in */; do if [ -f "$d"*.out ]; then echo ja; else echo nein; fi; done
bash: [: foo/abc.out: binary operator expected
nein

Wenn ich das richtig sehe, brauchst Du doch nur so etwas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
for d in caption/*/*/; do
  (
    cd "$d"

    for f in *.out; do
      test "$f" = '*.out' && qsub *.job
      break
    done
  )
done

oder, mit weniger Subshells

1
2
3
4
5
6
for d in caption/*/*/; do
  for f in "$d"*.out; do
    test -e "$f" || ( cd "$d"; qsub *.job )
    break
  done
done

Armani42

(Themenstarter)

Anmeldungsdatum:
28. Juni 2015

Beiträge: 7

Eine Frage noch: Wie kann ich nun Immer in den jeweiligen Ordner wechseln? Also wie lautet da die Syntax für

cd $d ${d%/}?

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

  1. Wieso hat bei Dir cd zwei Parameter dahinter ?? - was willst Du da erreichen ?

  2. Verwendest Du absolute Pfade oder relative Pfade ?
    - diesen Unterschied musst Du natürlich sauber im Blick behalten: denn bei relativen Pfaden musst Du natürlich immer vom tatsächlichen, aktuellen Arbeitsverzeichnis ausgehen, und den neuen Pfad natürlich auch von dort aus angeben.

LG,

track

Armani42

(Themenstarter)

Anmeldungsdatum:
28. Juni 2015

Beiträge: 7

Naja ich bin immer noch in dem Ordner wo die 18 drinnen sind und ich will nun in jeden der 18 Ordner rein und in die Ordner in den Ordnern also mit cd in 2 Ordner rein und dann wieder raus zu den 18 dann wieder cd in Iso2_Cd2Se2/Iso2_CdSe2_pbe0 und wieder zurück in den wo die 18 sind etc

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Du gehst auf meine Rückfragen überhaupt nicht ein ...

Ok, dann lies Dir wenigstens mal die Geschichte mit den absoluten und den relativen Pfaden durch,
und den Befehl cd im Bash-Manual. Beachte insbesondere den Pfad  -  als Kurzform für "$OLDPWR" .
Und noch einmal: denk an korrektes "Quoten" !

LG,

track

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

Armani42 schrieb:

Naja ich bin immer noch in dem Ordner wo die 18 drinnen sind und ich will nun in jeden der 18 Ordner rein und in die Ordner in den Ordnern also mit cd in 2 Ordner rein und dann wieder raus zu den 18 dann wieder cd in Iso2_Cd2Se2/Iso2_CdSe2_pbe0 und wieder zurück in den wo die 18 sind etc

Das habe ich doch in meinem Beispiel gezeigt.

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17630

Etwas late to the party, aber das: rklm schrieb:

1
2
3
4
5
6
for d in caption/*/*/; do
  for f in "$d"*.out; do
    test -e "$f" || ( cd "$d"; qsub *.job )
    break
  done
done

sieht mir nicht sehr effizient aus, codezeilenmäßig. Wieso 2 Schleifen?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

t201:~/proj/mini/forum/xx > tree
.
├── a
│   └── cur
├── b
│   └── cur
│       └── x.out
└── c
    └── cur

6 directories, 1 file
t201:~/proj/mini/forum/xx > for d in {a..c}/cur; do test -f $d/*.out || (cd $d; pwd) ; done 
/home/stefan/proj/mini/forum/xx/a/cur
/home/stefan/proj/mini/forum/xx/c/cur

Also übersetzt in die richtigen Pfad- und Programmnamen:

1
2
3
4
for d in caption/Iso{1..18}_Cd2Se2/Iso*_Cd2Se2_ezdpp
do
  test -f $d/*.out || (cd $d; qsub *.job) 
done  

Die Doppelpipe || sorgt dafür, dass das rechts davon nur ausgeführt wird, wenn der linke Teil gescheitert ist, also keine solche Datei existiert. Die runden Klammern (cd $d; qsub *.job) führen das Eingeklammerte in einer Subshell aus, so dass man danach wieder da ist, wo man vorher war, auch wenn das cd ... scheitert.

Ich muss aber noch etwas generelles loswerden.

In der Shell tippt man ungerne lange Pfadnamen wiederholt, also

1
cp furchtbar/langer/pfadname/a furchtbar/langer/pfadname/b

sondern lieber

1
2
3
cd furchtbar/langer/pfadname
cp a b
cd -

In einem Script schreibt man aber meist besser

1
2
d=furchtbar/langer/pfadname
cp $d/a $d/b

denn beim Hin- und Herhüpfen in Verzeichnissen können Dinge schiefgehen.

Vieles davon ist sicher eher theoretisch, etwa dass man in ein Verzeichnis von heute auf morgen nicht mehr reindarf oder dass das Verzeichnis gelöscht wurde. Wenn das Skript dafür nicht gewappnet ist, in dem es den Erfolg des Verzeichniswechsels überprüft hat oder durch einen Automatismus das Zurückspringen an das Reinspringen gekoppelt hat, kann übles passieren:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
t201:~/proj/mini/forum/xx > tree
.
├── a
│   └── cur
├── b
│   └── cur
│       └── x.out
└── c
    └── cur
t201:~/proj/mini/forum/xx > chmod a-x c 
t201:~/proj/mini/forum/xx > for d in {a..c}/cur; do test -f $d/*.out || (cd $d; pwd; cd -; pwd ) ; done 

/home/stefan/proj/mini/forum/xx/a/cur
/home/stefan/proj/mini/forum/xx
/home/stefan/proj/mini/forum/xx
bash: cd: c/cur: Keine Berechtigung
/home/stefan/proj/mini/forum/xx
/home/stefan/proj/mini/forum
/home/stefan/proj/mini/forum

Hier sind wir jetzt ein Verzeichnis zu hoch gelandet. Ich kenne jetzt Deine Daten nicht, ob da andere Verzeichnisse benachbart sind, die .job-Dateien enthalten, die womöglich unerfreuliche Ergebnisse erzeugen würden.

Absolute Pfade wären ein Hilfsmittel dagegen, aber einerseits bläht das den Code weiter auf, zweitens sind Scripte, die verschnupft auf Verschiebungen reagieren auch eine Fehlerquelle. Man kopiert die Verzeichnisstruktur nach woanders, wo man das gleiche tun will, und dann arbeitet das Script, dass unter /montag/caption war in /mittwoch/caption nicht, weil es die .out-Datei unter /montag/... gefunden hat.

Dagegen hilft eine Variable des Pfads am Anfang des Scripts, so dass man dieses nur einmal, an einer Stelle, anpassen muss, und es auch gleich bei einem flüchtigen Blick sieht.

Wenn qsub seine .out-Datei in das aktuelle Verzeichnis schreibt, dann hilft es aber nix, man muss das Verzeichnis wechseln (oder die .out-Datei nachträglich verschieben, was auch nicht eleganter ist). Bei Programmen, die die Ein- und Ausgabe parametrisieren lassen, würde man lieber

1
nichtQsub $d/*.job --output-dir=$d 

schreiben, aber das ist natürlich nur ab und an gegeben.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

user_unknown schrieb:

Etwas late to the party, aber das:

Je später der Abend, desto schöner die Gäste. 😉

rklm schrieb:

1
2
3
4
5
6
for d in caption/*/*/; do
  for f in "$d"*.out; do
    test -e "$f" || ( cd "$d"; qsub *.job )
    break
  done
done

sieht mir nicht sehr effizient aus, codezeilenmäßig. Wieso 2 Schleifen?

Weil "*.out" beliebig viele Dateinamen liefern kann. Man kann das nicht einfach so machen:

1
test -e "$d"*.out

Sonst passiert das:

1
2
3
4
$ touch a.out b.out
$ test -e *.out; echo $?
bash: test: a.out: binary operator expected
2
1
2
3
4
for d in caption/Iso{1..18}_Cd2Se2/Iso*_Cd2Se2_ezdpp
do
  test -f $d/*.out || (cd $d; qsub *.job) 
done  

Die Doppelpipe || sorgt dafür, dass das rechts davon nur ausgeführt wird, wenn der linke Teil gescheitert ist, also keine solche Datei existiert.

s.o.

Ich muss aber noch etwas generelles loswerden.

In der Shell tippt man ungerne lange Pfadnamen wiederholt, also

1
cp furchtbar/langer/pfadname/a furchtbar/langer/pfadname/b

sondern lieber

1
2
3
cd furchtbar/langer/pfadname
cp a b
cd -

In einem Script schreibt man aber meist besser

1
2
d=furchtbar/langer/pfadname
cp $d/a $d/b

👍

Vieles davon ist sicher eher theoretisch,

Da verkneife ich mir die routinemäßige Anmerkungen die Variablen mit den Verzeichnisnamen zu quoten und dann das von Dir! 😉

Wenn qsub seine .out-Datei in das aktuelle Verzeichnis schreibt, dann hilft es aber nix, man muss das Verzeichnis wechseln (oder die .out-Datei nachträglich verschieben, was auch nicht eleganter ist). Bei Programmen, die die Ein- und Ausgabe parametrisieren lassen, würde man lieber

1
nichtQsub $d/*.job --output-dir=$d 

schreiben, aber das ist natürlich nur ab und an gegeben.

👍

Antworten |