twohead
Anmeldungsdatum: 31. Januar 2009
Beiträge: Zähle...
|
Hi, was für ein toller Titel ^^. Folgendes Problem in einem Bashskript | #!/bin/bash
COUNTER=0
ls -1 | while read LINE
do
let COUNTER+=1
# verarbeite Zeile
done
echo $COUNTER
|
In der while wird COUNTER fleissig hoch gezählt. Kann man mit echo/ set -vx gut nachvollziehen. Aber nach der while ist COUNTER plötzlich wieder 0. Wieso das? Wie kann ich das verhindern? LG,
PS: Ich arbeite gerne mit set -e. Wenn ich aber eine Subtraktion (mit let VAR=3-3) habe, die zu 0 führt, dann gibt es einen Fehler ($? = 1), weiß jemand wie man zu 0 subtrahiert ohne auf set -e verzichten zu können?
|
Antiqua
Anmeldungsdatum: 30. Dezember 2008
Beiträge: 4534
|
willst du wc -l in bash implementieren? 😉 COUNTER=$(ls -l |wc -l); let COUNTER-=1; echo $COUNTER das macht genau, was du willst, inklusive Abzug von 1 und ohne while-Schleife - oder willst du die erste Zeile der Ausgabe von ls -l auch mitgezählt haben?
|
twohead
(Themenstarter)
Anmeldungsdatum: 31. Januar 2009
Beiträge: Zähle...
|
Hey, nein das will ich eigentlich nicht. Ich hab mit dem Kommentar
# verarbeite Zeile
versucht anzudeuten das ich da schon etwas sinnvolleres mehr mache. Aber das hat ja mit meinem eigentlich Problem gar nichts zu tun. Deshalb hab ich das der Einfachheit halber (Minimalbeispiel...) mal weggelassen. Hast du das Verhalten denn erwartet und kannst es reproduzieren? LG
|
Antiqua
Anmeldungsdatum: 30. Dezember 2008
Beiträge: 4534
|
twohead schrieb: Hey, nein das will ich eigentlich nicht. Ich hab mit dem Kommentar
# verarbeite Zeile
versucht anzudeuten das ich da schon etwas sinnvolleres mehr mache.
sorry, hab ich so nicht verstanden
Aber das hat ja mit meinem eigentlich Problem gar nichts zu tun.
da hast du allerdings recht
Deshalb hab ich das der Einfachheit halber (Minimalbeispiel...) mal weggelassen. Hast du das Verhalten denn erwartet und kannst es reproduzieren?
ich hab es nicht erwartet (verwende eigentlich so gut wie nie while), kann es aber reproduzieren... eventuell sorgt aber das ange-pipte while-Konstrukt dafür, daß bei FALSE eben die Variable COUNTER wieder auf den ursprünglichen Wert gestellt wird (ka. ob das stimmt, sieht aber zumindest so aus)
LG
Gz
|
Greebo
Anmeldungsdatum: 21. November 2006
Beiträge: 3443
Wohnort: 97070 Würzburg
|
Ja ihr seid auf dem richtigen Weg (Pipe/Subshell) ☺. Unter folgendem Link hatte jemand schonmal dasselbe Problem, dort wirds nochmal erklärt und eine mögliche Lösung angegeben.
|
twohead
(Themenstarter)
Anmeldungsdatum: 31. Januar 2009
Beiträge: 51
|
Genau so etwas hatte ich befürchtet. Vielen Dank für den Link. Leider funktioniert die Lösung dort nur für Dateien. Ich will ja aber die Ausgabe von einem Befehl zeilenweise einlesen, also die Standardeingabe. Die lässt sich leider nicht exakt so (überhaupt?) in die while biegen, dass keine Subshell gestartet wird. Weiß jemand wie man das macht? Damit nicht jeder im Link suche muss, die vorgeschlagene Lösung dort ist:
| counter=0
while read line; do
let ++counter
done < tmpfile.tmp
echo $counter
|
LG
|
Greebo
Anmeldungsdatum: 21. November 2006
Beiträge: 3443
Wohnort: 97070 Würzburg
|
Die einzige Lösung die ich gerade sehe ist auf die Subshell zu verzichten und dein while umzuformen, also statt der Pipe sowas wie
COUNTER=0
for LINE in `ls -l`; do
let ++COUNTER
done
echo $COUNTER
|
twohead
(Themenstarter)
Anmeldungsdatum: 31. Januar 2009
Beiträge: 51
|
Nur das die Eingabe dann nicht Zeilenweise eingelesen wird, sondern "Whitespace-weise". Deshalb benutz ich ja diese while Konstruktion, weil ich explizit Zeilenweise vorgehen will...
|
Antiqua
Anmeldungsdatum: 30. Dezember 2008
Beiträge: 4534
|
das mit dem zeilenweise lesen bei for-Schleifen dürfte ein
IFS=$'\012' # entspricht einem $'\n' Newline
lösen... also sowas dürfte funktionieren (tuts in der Tat bei mir): #!/bin/sh
LS=`ls -l`
IFS=$'\012'
COUNTER=0
for i in $LS ;
do
let ++COUNTER
echo $i
echo $COUNTER
echo "neue Zeile, oder was immer du machen willst"
done
echo $COUNTER
|
twohead
(Themenstarter)
Anmeldungsdatum: 31. Januar 2009
Beiträge: 51
|
Das ist ja hochinteressant. Wofür genau steht denn dieses Zeichen? Ich hab mich letzens schon nach einer Tabelle totgesucht, in der die Tasten den Escapesequencen zugeorndet werden (^M für Return glaube ich). Ich hab übrigens nix gefunden. Ich habs jetzt noch ausführlicher getestet. Bei ls -l passt alles. Aber bei dem Skript das ich eigentlich bearbeite kommt die Standardausgabe nicht von ls, sondern von einem cplex Skript. Und dort stimmt dann am Ende die Zeilenanzahl von der neuen Variante nicht mit der neuen Variante überein! 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | #!/bin/bash
/homes/combi3/Software/ILOG/ilm/ilmlist | uniq | sed 1,9d > /tmp/ls.txt
rm -f /tmp/1.txt
rm -f /tmp/2.txt
COUNTER=0
while read LINE;
do
let ++COUNTER
echo "$LINE" >> /tmp/1.txt
done < /tmp/ls.txt
echo $COUNTER
IFS=$'\012'
COUNTER=0
for LINE in `cat /tmp/ls.txt`;
do
let ++COUNTER
echo "$LINE" >> /tmp/2.txt
done
echo $COUNTER
|
Die Ausgabe ist:
% ./test1
13
11 D.h. die Zeilen werden anders gezählt! % diff /tmp/1.txt /tmp/2.txt
4,5c4
<
< * PRODUCT CPLEX: 10 any
---
> * PRODUCT CPLEX: 10 any
8,9c7
< available tokens: 7
<
---
> available tokens: 7 Die neue Variante überspringt Leerzeilen!
|
Antiqua
Anmeldungsdatum: 30. Dezember 2008
Beiträge: 4534
|
in /tmp/ls.txt scheinen 3 Leerzeilen zu sein. An einem bash-Promt gibt
for LINE in `cat test.txt`; do echo $LINE; done nur Zeilen aus, in denen mehr als ein /n steht, im Gegensatz zu
cat test.txt das gibt auch die (bis auf das Newline leeren) Zeilen aus es ist übrigens mE. eine gute Idee, daß man das Umbiegen von $IFS nur direkt vor der Funktion macht und das gleich wieder zurückstellt.
Also z.B. ...
OLDIFS=$IFS
IFS=$'\012'
for LINE in bla ... ;
do
...
done
IFS=$OLDIFS
...
eventuell kannst du ja mal eine Beispieldatei geben und mal genau sagen, was du überhaupt bezweckst... Ich bin mir sicher, daß sich das Problem irgendwie einfacher, bzw. ohne so üble Hacks wie $IFS verbiegen, lösen lässt. Zur Not mit einer anderen Programiersprache 😉
|
twohead
(Themenstarter)
Anmeldungsdatum: 31. Januar 2009
Beiträge: 51
|
Jo klar wäre das in Perl schöner. Ich brauche sowas aber in letzter Zeit relativ häufig in kleinsten Bash Skripten. Kannte das noch gar nicht und das wird doch höchstens für den Aufruf des Skript verbogen. Finde das ne interessante Sache. Wofür genau steht denn nun das Zeichen IFS=$'\012'? LG
|
Greebo
Anmeldungsdatum: 21. November 2006
Beiträge: 3443
Wohnort: 97070 Würzburg
|
twohead schrieb: Jo klar wäre das in Perl schöner. Ich brauche sowas aber in letzter Zeit relativ häufig in kleinsten Bash Skripten. Kannte das noch gar nicht und das wird doch höchstens für den Aufruf des Skript verbogen. Finde das ne interessante Sache. Wofür genau steht denn nun das Zeichen IFS=$'\012'? LG
Hat Antiqua doch schon hier geschrieben als Separator wird das Zeichen \012 (oktal?) angegeben, was nem Linefeed entspricht. Damit trennt das for seine Datensätze Zeilenweise statt Whitespaceweise. Falls AWK zur Verfügung steht würde ich übrigens das vorschlagen. AWK ist genau für solche Sachen gedacht.
|
Antiqua
Anmeldungsdatum: 30. Dezember 2008
Beiträge: 4534
|
$IFS ist eine Builtin Variable der bash (s. http://tldp.org/LDP/abs/html/internalvariables.html#IFSREF) man bash
IFS The Internal Field Separator that is used for word splitting after expansion
and to split lines into words with the read builtin command. The default
value is ``<space><tab><newline>''.
und das \012 ist einfach die oktale Schreibweise von \n und das ist das ASCII-Steuerzeichen für LineFeed (s. ASCII control characters) und wird durch IFS=$'\012' eben der Internal-Field-Separator-Variablen zugewiesen. Dadurch wird das <leerzeichen> und \t als Feldseparator entfernt und nur noch \n "scharfgeschaltet".
|
twohead
(Themenstarter)
Anmeldungsdatum: 31. Januar 2009
Beiträge: 51
|
Greebo schrieb: Hat Antiqua doch schon hier geschrieben als Separator wird das Zeichen \012 (oktal?) angegeben, was nem Linefeed entspricht. Damit trennt das for seine Datensätze Zeilenweise statt Whitespaceweise. Falls AWK zur Verfügung steht würde ich übrigens das vorschlagen. AWK ist genau für solche Sachen gedacht.
Das hab ich glatt übersehen. Hatte bloss den kompletten Codeblock darunter im Blick. Danke. Großes Danke auch an Antiqua für die nochmals ausführlichere Erklärung. Super Hilfe, Dankeschön! Awk steht zur Verfügung. Hab ich auch mal sehr kurz drauf geguckt. Aber hab ich noch nie richtig benutzt. Ich will die ersten X Zeilen ignorieren. Ich identifiziere Zeile X an dem Vorhandensein eines String am Anfang
if [ ${LINE:0:9} = "ABSCHNITT" ] ; then
break;
fi
Dann steht der Counter auf der entsprechenden Zeilennummer und ich kann mit sed 1,${COUNTER}d den ersten Abschnitt wegschmeißen. Wie würde das mit awk gehen?
|