Több soros sztringet szétvágni szabvány shellben

Sziasztok, a szabvány shell parancssorában viszek be egy többsoros szöveget:

user@host~: sh -c 'a=" asdf

> mnb"'; a=${a#*d}; echo "$a";'

ez jól működik, viszont egyelőre sehogy sem tudom megoldani, hogy a \newline-nál csapjon le ebből a többsoros szövegből.

Szerintetek hogy tudnám ezt kifejezni szabvány shell-ben?

Hozzászólások

Szerkesztve: 2020. 02. 08., szo - 12:21

Játszottam vele:

[user@host ~]$ a=" asdf
> mnb"
[user@host ~]$ p=$'\n'
[user@host ~]$ echo "${a#*$p}"
mnb

Erre vágytál?

Szerk.: Ez kimaradt: bash-5.0.11-1.fc31.x86_64

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

sajnos ez csak bash-hal működik, ott nem is lenne kérdés, de nekem dash vagy bármi alatt kéne, ezért is hangsúlyoztam és hangsúlyozom, hogy szabvány shell alatt kell, hogy működjön, és nem értem még, hogy hogy fejezzem ki a \newline-t, úgy, hogy működjön (ha változóba raktam, akkor is úgy tűnt, hogy csak sima karakterre ment)....

Muszáj ezt shellben? Esetleg sed vagy awk nem jöhet szóba? Vagy tr? Mondanám azt, hogy a shell nem erre való, ráadásul van shell, ami tudja is. De, hogy épp dash-sel hogyan lehet megcsinálni, azt nem tudom. Van Fedorára is dash, de most így szombaton inkább mást csinálok. :) Egyébként bash közvetlenül is megengedi:

echo "${a#*$'\n'}"

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Most nem próbáltam ki, de read vagy cut nem játszik?

Igen, szabyánv shellben érdekel, hogy ne csak más karakterre, hanem \n -re is tudjak vágni, ez a kihívás, és nem értem még a logikáját, az ellenőriztem, hogy \012 -ként rátolja el az újsort, tényleg, nincs semmi trükk, de a kifejezésben erre vágni még nem tudok. Szóval ez a kihívás:

read: a szabvány shellben a read a leírások szerint és a kísérleteim szerint TÉNYLEG csak STDIN-ről olvas.

Nem szeretném temp fájlba kiíratni a cuccot, nem szeretnék subshelt nyitni, amiből megint csak mindig macera az eredmény visszakapása, de mindképpen alapvetően az a kérdés, hogy a szabvány shellben egy változóban lévő tetszőleges oktet-sorozatot csak az ASCII vagy bármelyik karakterkódú karakternél el tudom-e vágni :)

Szerkesztve: 2020. 02. 08., szo - 13:31
$ dash
$ v="első
> második
> harmadik"
$ echo "${v%%
> *}"
első

Ugyan bash-sel, de kipróbáltam a newline bevitelét közvetlenül, amikor még nem volt hozzászólás a topic-odban, működött is, de nem említettem, mert annyira ronda megoldásnak éreztem, hogy mindenképp valami jól megfogalmazható megoldásra vágytam. Igaz, akkor sem tudtam volna meg, hogy dash-sel működik-e, vagy sem. De mindegy is, már okafogyott, lényeg, hogy megvan a megoldás.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Na most, ha mégis \n-es formában akarod, akkor a Caro által javasolt read is megoldás lehet:

#!/bin/sh

ORISTR="nulladik\nelső\nmásodik\nharmadik\nnegyedik"

get_nldl_str_elem()
{
	I=0
	echo "$1" | while read -r line;
	do
		if [ "$I" = "$2" ];
		then
			echo $line
			exit
		fi
		I=$((I+1))
	done
}

Használat:

echo `get_nldl_str_elem "$ORISTR" 1`

Ez persze nem a legszebb (POSIX shellben nincsenek tömbök, így nem lehet egyszer tömbbé alakítani), de működik. Aki akar finomíthat rajta.

á, igen, ez nem ugrott be, hogy miért nem pipe-ból read-elek, ez kellett nekem, asszem:

user@host:~/d$ sh -c 'echo " asdf
mnb" | while read -r line; do echo "$line ."; done'
asdf .
mnb .

pseudo indexelt tömböket szoktam használni eval segítségével létrehozva, olvasva.

Dehát pont erre adtam egy példát, ahol függvényből kérjük le az egyes elemeket. Ezt minden további nélkül át lehet írni hosszra is.

#!/bin/sh

ORISTR="nulladik\nelső\nmásodik\nharmadik\nnegyedik"

get_nldl_str_elem_len()
{
	I=0
	echo "$1" | while read -r line;
	do
		if [ "$I" = "$2" ];
		then
			echo $line | wc -m
			exit
		fi
		I=$((I+1))
	done
}

Bármi, amit egy subshellben (UNIX shellben a "függvény" is csak egy subshell) kiírunk a stdout-ra, azt el lehet kapni a subshell operátorokkal és fel lehet használni. Tényleg nem értem a problémát.

ez igaz, ha bent hagyom függyvényben, akkor kezelhetőbb ebből a szempontból, a wc nem játszik, mert ugye shellben maradok, több irányban is gondolkozom, hogy mi legyen a kód, amiben végül is egy ASCII art karaktereit elemzem és kezelem, amihez először is meg akarom állapítani a dimenzióit. Most egyelőre azon az úton vagyok, hogy a fő ciklusomban karakterről karakterre olvasom, asszem végül is így fogom megkapni, hogy hány oszlopos, hány soros, ami nekem kell

önmagában nekem is jól csinálja, de aztán valahol meg azt írta, hogy 3 5 3, bár most van egy valamennyire működő megoldásom, de nyilván én is az egész feladatra a legjobb, legrövidebb és legalapabb-legbiztosabb megoldást szeretném, hogy a kapott értékekkel a legjobban dolgozhassak, igazából az lesz a program, hogy egy ascii-artot oszlopokra hasogassak és paramétertől függően csal egy részét mutassam rajzoljam ki a szabvány terminál bal oldalán...

Szerkesztve: 2020. 02. 08., szo - 17:41

azért, mert ahogy írtam, szabvány shellben kell az eredmény, itt tart a projekt:

a="
 asd
qwe r
 v x";

l=${#a}; w=0; c=2; aw=0; while [ $c -le $l ]; do a=${a#?};
 cc=`printf "%d" "\"$a"`;

  if [ $cc -ne 10 ]; then aw=$((aw+1)); else echo $aw; aw=0; fi;

c=$((c+1)); done; echo

A wc egy külső parancs, amit szabvány shellből is meg lehet hívni. Ami pedig a subshell-t illeti, ha subshellbe rakod a ciklust is, akkor ki tudod echozni belőle a változódat:

#!/bin/sh

a="
 asd
qwe r
 v x"

MAXW=0
RMAXW=$(echo "$a" | (while read -r line;
do
	CW=`echo "$line" | wc -m`
	if [ "$MAXW" -le "$CW" ];
	then
		MAXW="$CW"
	fi
done
echo $MAXW))
echo $RMAXW

Így a RMAXW változóban megkaptad a legszélesebb sor hosszát, azaz az ASCII-art valódi szélességét. És ez POSIX shellből is megy.

"Pazarol el?" Lássuk csak...
Soronként három processzt indít: egy read-et, egy echo-t és egy wc-t. Ha van egy "teljes képernyős" (80x25)-ös ASCII-artunk, akkor ez összesen 75 processzt fog indítani.
A másik karakterenként indít egy printf-et. Az összesen 2025 processz egy 80x25-ös ASCII-art esetén, mivel ott a newline is számít.
Azaz 75-2025=-1950 processzt "pazarol el".

"Soronként három processzt indít: egy read-et, egy echo-t"

Hogymi?!

A read mindenképpen shell builtin, különben nem tudná a shellben látható környezeti változó értékét változtatni.

Az echo lehet külön processz, lásd /bin/echo, de a legtöbb shellben builtin, éppen az extra processzek overheadjének elkerülése miatt.  A printf szintén.

Cserébe viszont ott vannak a subshellek a pipe-oknak és a command substitution-öknek...

> A read mindenképpen shell builtin, különben nem tudná a shellben látható környezeti változó értékét változtatni.

The read utility historically has been a shell built-in. It was separated off into its own utility to take advantage of the richer description of functionality introduced by this volume of POSIX.1-2017.

Azaz lehet külön és builtin is, de akkor legyen builtin. Az mínusz 25 processz.

> Az echo lehet külön processz, lásd /bin/echo, de a legtöbb shellben builtin, éppen az extra processzek overheadjének elkerülése miatt. A printf szintén.

A echo és a printf a POSIX shellben nem builtin.

> Cserébe viszont ott vannak a subshellek a pipe-oknak és a command substitution-öknek...

A subshellek? Két subshell van és mind a kettő a cikluson kívül. Az összesen kettő subshell. A pipe és a substitution ugyan ott van, de közben Zahy fentebb mutatott jobbat. Azaz:

CW=${#line}

És ezzel a rössel megspóroltuk a substitution-t, az echo-t a pipe-t és a wc-t is. Akkor soronként 0 processz. Szemben a 2025 printf-fel és 2025 subsitutionnel.

Az, hogy egy parancs a "Utilities" fejezetben kapott helyet, nem jelenti azt, hogy azt nem lehet builtinként implementálni.

Lásd pl. alias, cd, getopts, read, ulimit, amik mind abban a fejezetben vannak, de nem lehet őket külső parancsként implementálni.

A pipe és a command substitution is subshelleket használ.

Tényleg? Nézzük meg Zahy megoldása nélkül az eredeti javaslatomat:

#!/bin/sh

a=`cat "$1"`

MAXW=0
RMAXW=$(echo "$a" | (while read -r line;
do
	CW=`echo "$line" | wc -m`
	if [ "$MAXW" -le "$CW" ];
	then
		MAXW="$CW"
	fi
done
echo $MAXW))
echo "Width: ""$RMAXW"" ch."
root@Csabi:~# time for i in {1..50}; do asciiwidetest1 asciiart.txt >/dev/null; done

real    0m3,235s
user    0m0,144s
sys     0m0,272s

Ez még így is ~11x gyorsabb, mint a karakterenkénti feldolgozás (~36 sec vs. ~3.2 sec).

> Mondjuk a ciklus körüli subshell még mindig felesleges...

Nem az. Ha leszedem, a végeredmény nulla lesz. Próbáld ki.

"Tényleg? Nézzük meg Zahy megoldása nélkül az eredeti javaslatomat:

~3.2 sec"

Hát bizony, a pipe és wc sokkal lassabb, mint a shell-től megkérdezni a string hosszát.

echo "Width: ""$RMAXW"" ch."

Höhö.

"> Mondjuk a ciklus körüli subshell még mindig felesleges...

Nem az. Ha leszedem, a végeredmény nulla lesz. Próbáld ki."

De felesleges, csak nem jól szedted le:

RMAXW=$(while read -r line
do
    ......
done <"$1"
echo $MAXW)
echo "Width: $RMAXW ch."

> Hát bizony, a pipe és wc sokkal lassabb, mint a shell-től megkérdezni a string hosszát.

Nem mondod... Lényeg megvolt, hogy a karakteres feldolgozáshoz képest még így is 11x gyorsabb volt?

> De felesleges, csak nem jól szedted le:

A megfordítás tényleg nem jutott eszembe. Viszont az érintett subshell egyszer futott az egész script alatt, úgyhogy nem túl sok vizet zavart.

"Nem mondod... Lényeg megvolt, hogy a karakteres feldolgozáshoz képest még így is 11x gyorsabb volt?"

Nem, a lényeg az, hogy a subshellek és a külsö processzek drágák.

A karakterenkénti feldolgozás önmagában nem baj, a karakterenkénti subshell volt ott az igazi gond.  A subshellek és a külső processz még soronként is gond: egy tisztességesen implementált karakterenkénti feldolgozás több mint ötször gyorsabb tud lenni, mint a soronként $(echo "$line" | wc -m)-et futtató változat.

"Viszont az érintett subshell egyszer futott az egész script alatt, úgyhogy nem túl sok vizet zavart."

Az érintett subshellel együtt a script elején lévő $(cat "$1") is mehetett a levesbe, és ezek együtt kb 30% gyorsulást jelentenek.  Jobban megnézve, a ciklus körüli command substitution is teljesen felesleges, anélkül már 40%-nál járunk.

> Nem, a lényeg az, hogy a subshellek és a külsö processzek drágák.

> A karakterenkénti feldolgozás önmagában nem baj, a karakterenkénti subshell volt ott az igazi gond.

Meg is válaszoltad magadnak. A másik karakterenként csinált subshellt és printf call-t. Na, az az igazi pazarlás.

> A subshellek és a külső processz még soronként is gond: egy tisztességesen implementált karakterenkénti feldolgozás több mint ötször gyorsabb tud lenni, mint a soronként $(echo "$line" | wc -m)-et futtató változat.

Viszont, ha nincs soronként külső processz, meg subshell, akkor a soronkénti feldolgozás mindenképpen sokkal gyorsabb lesz, mint a karakterenkénti feldolgozás, mert kevesebbet kell interpreterből iterálni. (Egyébként mit értesz "tisztességesen implementált" karakterenkénti feldolgozáson?)

> Az érintett subshellel együtt a script elején lévő $(cat "$1") is mehetett a levesbe, és ezek együtt kb 30% gyorsulást jelentenek. Jobban megnézve, a ciklus körüli command substitution is teljesen felesleges, anélkül már 40%-nál járunk.

Ez mondjuk jogos, a cat tényleg sokat nyomott.

Tudom, hogy meg lehet, végül is a progi ezen fázisának ez lett kb a kódja:

a="
 asd
qwe r
 v x";

l=${#a}; w=0; c=2; aw=0; while [ $c -le $l ]; do a=${a#?};
 cc=`printf "%d" "\"$a"`;

  if [ $cc -ne 10 ]; then aw=$((aw+1));
  elif [ $aw -gt $w ]; then w=$aw; aw=0; fi;

c=$((c+1)); done; echo "Width: $w ch.";

hm, na most ezen elgondolkoztam, mert én bash-t nem használok, de dash-ban is van builtin printf, és olyan megfogalmazásban is olvastam, hogy ez POSIX builtin utility, most akkor, látva, hogy printf-em van a különféle shellekben, még dash-ban is, szóval használom, de végül is érdekel, hogy akkor most mi az abszolút igazság??

A dash egy konkrét shell, ami nem 100% POSIX compliant. (http://man7.org/linux/man-pages/man1/dash.1.html)
Például pont a builtin printf-nek van is nem javított bugja, ami töri a POSIX-kompatibilitást (mondjuk téged pont nem érint).

Te egész konkrétan szabvány shell megoldást kértél, én azt adtam, egy szóval nem mondtad, hogy dash-specifikus legyen.

viszonylag sokat olvastam most arról, hogy akkor mi a fene van az echo-val és a printf-fel, ha jól értem, azt mondja a POSIX, hogy valahol legyen:

https://www.unix.com/man-page/posix/1posix/printf/

a ${#var} is helyenként szarul működni látszott, úgy, hogy nem vette figyelembe a sor eleji szóközöket, ami viszont biztos nem opció nekem, szóval, most ez van, a tuti külső processzindításokat kerülném:

a="
 asd
qwe r
 v x";

l=${#a}; w=0; c=2; aw=0; while [ $c -le $l ]; do a=${a#?};
 cc=`printf "%d" "\"$a"`;

  if [ $cc -ne 10 ]; then aw=$((aw+1));
  elif [ $aw -gt $w ]; then w=$aw; aw=0;
  elif [ $aw -le $w ]; then aw=0;
  else aw=-1; fi;

c=$((c+1)); done; echo "Width: $w ch.";

Benchmarkoltam.

A tiéd:

#!/bin/sh

a=`cat "$1"`

l=${#a}; w=0; c=2; aw=0; while [ $c -le $l ]; do a=${a#?};
 cc=`printf "%d" "\"$a"`;

  if [ $cc -ne 10 ]; then aw=$((aw+1));
  elif [ $aw -gt $w ]; then w=$aw; aw=0;
  elif [ $aw -le $w ]; then aw=0;
  else aw=-1; fi;

c=$((c+1)); done; echo "Width: $w ch.";

Az enyém:

#!/bin/sh

a=`cat "$1"`

MAXW=0
RMAXW=$(echo "$a" | (while read -r line;
do
	CW=${#line}
	if [ "$MAXW" -le "$CW" ];
	then
		MAXW="$CW"
	fi
done
echo $MAXW))
echo "Width: ""$RMAXW"" ch."

A tesztfájl (RAW itt: http://oscomp.hu/depot/asciiart.txt):

    888888888888888888888
  s 88 ooooooooooooooo 88     s 888888888888888888888888888888888888888
  S 88 888888888888888 88    SS 888888888888888888888888888888888888888
 SS 88 888888888888888 88   SSS 8888                         - --+ 8888
 SS 88 ooooooooooooooo 88  sSSS 8888           o8888888o         | 8888
sSS 88 888888888888888 88 SSSSS 8888         o88888888888o         8888
SSS 88 888888888888888 88 SSSSS 8888        8888 88888 8888      | 8888
SSS 88 ooooooooooooooo 88 SSSSS 8888       o888   888   888o       8888
SSS 88 888888888888888 88 SSSSS 8888       8888   888   8888       8888
SSS 88 888888888888888 88 SSSSS 8888       8888   888   8888       8888
SSS 88 oooooooooo      88 SSSSS 8888       8888o o888o o8888       8888
SSS 88 8888888888 .::. 88 SSSSS 8888       988 8o88888o8 88P       8888
SSS 88 oooooooooo :::: 88 SSSSS 8888        8oo 9888889 oo8        8888
SSS 88 8888888888  `'  88 SSSSS 8888         988o     o88P         8888
SSS 88ooooooooooooooooo88  SSSS 8888           98888888P           8888
SSS 888888888888888888888__SSSS 8888                               8888_____
SSS |   *  *  *   )8c8888  SSSS 888888888888888888888888888888888888888
SSS 888888888888888888888.  SSS 888888888888888888888888888888888888888
SSS 888888888888888888888 \_ SSsssss oooooooooooooooooooooooooooo ssss
SSS 888888888888888888888  \\   __SS 88+-8+-88============8-8==88 S
SSS 888888888888888888888-. \\  \  S 8888888888888888888888888888
SSS 888888888888888888888  \\\  \\       `.__________.'      ` .
SSS 88O8O8O8O8O8O8O8O8O88  \\.   \\______________________________`_.
SSS 88 el cheapo 8O8O8O88 \\  '.  \|________________________________|
 SS 88O8O8O8O8o8O8O8O8O88  \\   '-.___
  S 888888888888888888888 /~          ~~~~~-----~~~~---.__
 .---------------------------------------------------.    ~--.
 \ \______\ __________________________________________\-------^.-----------.
 :'  _   _ _ _ _  _ _ _ _  _ _ _ _   _ _ _           `\        \
 ::\ ,\_\,\_\_\_\_\\_\_\_\_\\_\_\_\_\,\_\_\_\           \      o '8o 8o .
 |::\  -_-_-_-_-_-_-_-_-_-_-_-_-_-___  -_-_-_   _ _ _ _  \      8o 88 88 \
 |_::\ ,\_\_\_\_\_\_\_\_\_\_\_\_\_\___\,\_\_\_\,\_\_\_\_\ \      88       \
    `:\ ,\__\_\_\_\_\_\_\_\_\_\_\_\_\  \,\_\_\_\,\_\_\_\ \ \      88       .
     `:\ ,\__\_\_\_\_\_\_\_\_\_\_\_\____\    _   ,\_\_\_\_\ \      88o    .|
       :\ ,\____\_\_\_\_\_\_\_\_\_\_\____\  ,\_\ _,\_\_\_\ \ \      'ooooo'
        :\ ,\__\\__\_______________\__\\__\,\_\_\_\,\___\_\_\ \
         `\  --  -- --------------- --  --   - - -   --- - -   )____________
           `--------------------------------------------------'

A teszt:

root@Csabi:~# time for i in {1..50}; do asciiwidetest0 asciiart.txt >/dev/null; done

real    0m35,913s
user    0m3,760s
sys     0m11,236s
root@Csabi:~# time for i in {1..50}; do asciiwidetest1 asciiart.txt >/dev/null; done

real    0m0,282s
user    0m0,008s
sys     0m0,008s

Azaz a tiéd 50 elemzést kb. 36 másodperc alatt csinált meg, az enyém pedig kb. 1/4 másodperc alatt. A különbség 127.351x-es. Ekkora overheaddel jár, ha karakterenként dolgozol fel egy interpretált nyelvből a soronkénti feldolgozáshoz képest.

izgalmas, köszi, kezd kifejezetten érdekes lenni számomra, hogy ez a fajta soronként feldolgozás megbízható-e, mert elég kompatibilisnek tűnik, és nekem tetszik, ha működik, akkor mégis ezen az úton mennék tovább, tesztelem én is, kösz

Nincs mit, de ha azzal kezded, hogy az a feladat, hogy "igazából az lesz a program, hogy egy ascii-artot oszlopokra hasogassak és paramétertől függően csal egy részét mutassam rajzoljam ki a szabvány terminál bal oldalán", akkor már rég mondtam volna, hogy ne szenvedj shell-el. Ezt jobb megírni mondjuk C-ben:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>

int f2mem(char *path, char **dst_out, size_t *size)
{
	FILE *f;
	size_t l;
	char *buf;

	*dst_out = NULL;
	f = fopen(path, "rb");
	if (f == NULL)
	{
		return 1;
	}
	fseek(f, 0, SEEK_END);
	l = ftell(f);
	buf = malloc(l);
	if (buf == NULL)
	{
		fclose(f);
		return 2;
	}
	fseek(f, 0, SEEK_SET);
	fread(buf, 1, l, f);
	fclose(f);
	*size = l;
	*dst_out = buf;
	return 0;
}

int main(int argc, char *argv[])
{
	int r, i;
	char *buf, c, col, row, start, stop;
	bool copy;
	size_t size;

	if (argc != 4)
	{
		fprintf(stderr, "Usage: getcols <file> <start> <stop>\n");
		return 0;
	}
	start = strtol(argv[2], NULL, 10);
	if (errno != 0)
	{
		fprintf(stderr, "Erroneous start column: %s\n", argv[2]);
		return 3;
	}
	stop = strtol(argv[3], NULL, 10);
	if (errno != 0)
	{
		fprintf(stderr, "Erroneous stop column: %s\n", argv[3]);
		return 4;
	}

	r = f2mem(argv[1], &buf, &size);
	if (r == 1)
	{
		fprintf(stderr, "Cannot open file: %s\n", argv[1]);
		return 1;
	}
	if (r == 2)
	{
		fprintf(stderr, "Cannot allocate memory.\n");
		return 2;
	}
	i = 0;
	row = 0;
	col = 0;
	copy = false;
	while (i < size)
	{
		c = buf[i++];
		if (c == 10)
		{
			col = 0;
			++row;
			copy = false;
		}
		else
		{
			if ((col == start) && (!copy))
			{
				copy = true;
			}
			if (copy)
			{
				fprintf(stdout, "\033[%d;%dH%c", row, col - start, c);
			}
			if ((col == stop) && (copy))
			{
				copy = false;
			}
			++col;
		}
	}
	free(buf);
}

Ez pontosan azt csinálja, amit szeretnél: N db oszlopot kicopy-z a fájlból és kiírja a terminálra. Leteszteltem, működik, persze bug lehet benne.

Sz*rk: Frissítettem a programot: beleraktam, hogy magától mozgassa a képernyő bal felére a tartalmat, escape szekvenciákkal.

Kíváncsi lettem. ;)

real    0m2.624s
user    0m1.417s
sys     0m1.194s

real    0m0.015s
user    0m0.007s
sys     0m0.008s

real    0m0.005s
user    0m0.002s
sys     0m0.003s

Az utóbbi megoldás:

#!/bin/sh

awk \
'{
    if ( length() > L )
        L=length()
}
END {
        print L
    }' $1

Bár nem felel meg a specifikációnak. :(

Iderakom a C programodat is:

real    0m0.002s
user    0m0.001s
sys     0m0.001s

Bár ekkora és ilyen bonyolult C programot még nem láttam. ;)

A lényeg: Vagy 30 éve junixozom, dolgoztam fel már 30M sort is, ami ennél sokkal bonyolultabbnak tűnt ;), de szöveget soha nem shellben, mert nem arra való. (Mert akkor sqlben is lehetne.:)) Úgy 1-1,5M rekordig awk - ha nincs különös követelmény, felette C.

Az nem baj, hogy a benchmarkolt shell kódok és a C kód teljesen mást csinál? :)

Biztos jó amúgy erre az oszlopcopyzásra az awk is, de én meg azt nem ismerem annyira. (Fogadnék egy teszkógazdaságos gumikecskepörköltbe, hogy tovább tartott volna megkeresnem, hogy awk-ból hogy oldom meg, mint ameddig megírtam a C-s verziót.) Amúgy is, célfeladatra céleszköz; ez pontosan azt csinálja, amit sas kért, nem kell bűvészkedni mindenféle paraméterekkel, ráadásul ez 10 kB, az awk meg ~650 kB. :P

:-D

Némi túlzással, ha odalépsz egy programozó mögé, akkor első pillantásra el sem tudod dönteni, hogy C-ben, awk-ban vagy shellben írja a kódot. Szóval a mondásodból csak annyi igaz, hogy nemigen dolgoztál még awk-ban.

A használt programnyelv kiválasztásánál egyik szempot csak annyi, hogy a C:awk kb. 1:6 futásidőt igényel. De tesztelésre is jó az awk, mert utána berakod a kódot C-be, vagy PIC assemblerbe :-D és pillanatok alatt át lehet írni. Legalábbis egyszerűbb feladatoknál, ahol nincs sok regexp. Ha meg van, akkor az awk győz az asszociatív tömbökkel. (Magyarázat: Bonyolultabb szöveges feldolgozásoknál általában szükség van szótárakra, sőt javító szótárakra is. Bár lehet ilyet írni, illetve biztosan meg is írták már mindenféle nyelvre, de az awk sokkal gyorsabb feladatmegoldást tesz lehetővé.)

Ehhez a feladathoz nagyságrendileg legfeljebb a rejtélyes substr() függvényre lehet szükség. ;)

A C programod hatalmas, biztosan jól meg van írva, de fix vagy maximált hosszú sorok feldolgozáskor

- nincs buffer - csak egy (max két) sornyi (nincs fopen, seek, meg ilyen marhaságok)

- nincs malloc()

- a fentiek alapján nyugodtan lehet használni a marhák szerint obsolete gets() függvényt

Ezzel 2/3 program ki is van dobva.

Az awk tényleg marhanagy. Én meg POWER-en programoztam, ahol jóval kisebbek a binárisok, mint egy pécén. :P

Ha már marháskodunk, ahol a tesztelést végeztem (linux) az awk mérete csak 418k, a hátam mögött (freebsd) csak 167k. :)

> Némi túlzással, ha odalépsz egy programozó mögé, akkor első pillantásra el sem tudod dönteni, hogy C-ben, awk-ban vagy shellben írja a kódot. Szóval a mondásodból csak annyi igaz, hogy nemigen dolgoztál még awk-ban.

Nem igazán világos, hogy mi az összefüggés a két mondat között, pláne aközött, amit én mondtam...

> A használt programnyelv kiválasztásánál egyik szempot csak annyi, hogy a C:awk kb. 1:6 futásidőt igényel. De tesztelésre is jó az awk, mert utána berakod a kódot C-be, vagy PIC assemblerbe :-D és pillanatok alatt át lehet írni. Legalábbis egyszerűbb feladatoknál, ahol nincs sok regexp. Ha meg van, akkor az awk győz az asszociatív tömbökkel. (Magyarázat: Bonyolultabb szöveges feldolgozásoknál általában szükség van szótárakra, sőt javító szótárakra is. Bár lehet ilyet írni, illetve biztosan meg is írták már mindenféle nyelvre, de az awk sokkal gyorsabb feladatmegoldást tesz lehetővé.)

Asszem elkerülte a figyelmedet a "célfeladatra céleszköz" kitétel. Ha regexp hegyekre lett volna szükség, nem veszem elő a C-t. De itt pár oszlopot kellett kicopyzni.

> Ehhez a feladathoz nagyságrendileg legfeljebb a rejtélyes substr() függvényre lehet szükség. ;)

> A C programod hatalmas, biztosan jól meg van írva, de fix vagy maximált hosszú sorok feldolgozáskor

> - nincs buffer - csak egy (max két) sornyi (nincs fopen, seek, meg ilyen marhaságok)

> - nincs malloc()

> - a fentiek alapján nyugodtan lehet használni a marhák szerint obsolete gets() függvényt

> Ezzel 2/3 program ki is van dobva.

Csak előrántottam a már ötvenezer éve megírt "rántsunk be egy fájlt memóriába" függvényemet és kész; lévén ASCII-artról beszélünk, tuti befér a memóriába, onnantól kezdve meg édes mindegy, hogy most hogy is van bufferelve. (De ha akarod, kimérheted, szerintem a soronkénti beolvasás lassabb lesz, mint az egybeni, mert az annyiszor annyi blokkművelet, még akkor is, ha az FS a cache-ből rántja elő a szektort.)

> Az awk tényleg marhanagy. Én meg POWER-en programoztam, ahol jóval kisebbek a binárisok, mint egy pécén. :P

> Ha már marháskodunk, ahol a tesztelést végeztem (linux) az awk mérete csak 418k, a hátam mögött (freebsd) csak 167k. :)

Most szét vannak szedve a PowerMac-ek, így nem tudom lemérni mennyi lenne PPC-n a bináris, de valószínűleg ha x86-on 10 kB volt, akkor POWER-en se lesz nagyobb, mint 418k vagy 167k. :P

Első idézet magyarázata: Valószínűleg nem sokat kellene tanulnod az awk használatához. Van egy-két specifikum, de azt felszedi az ember menet közben.

Második idézet magyarázata: Csak úgy l'art pour l'art írogattam az awk szépségéről és hasznosságáról. Semmi bajom a C-vel.

Harmadik idézet magyarázata: Az indoklás elfogadható szintű. ;)

A soronkénti beolvasás tényleg lassabb. Ha tudjuk, hogy ASCII-artról van szó, akkor kell mondani egy gets()-t és máris ott csücsül a default 5k bufferben a szöveg. Ezt az oprendszer és a shell biztosítja. Az awk esetén is.

A "célfeladatra céleszköz", ok nézd meg a kódomat. Gyorstalpaló és egyszerűsített awk tanfolyam:

- Egy program 3 opcionális részből állhat: BEGIN{} {(body)} END{}

- A body minden beolvasott sorra végrehajtódik, amíg a sorok el nem fogynak.

- A length() függvény paraméter nelkül az $0 változó hosszát adja vissza, ami nem más, mint a beolvasott sor. Ezért is nem féltem 2x meghívni, mert ez a hossz már a beolvasáskor előáll.

- A numerikus változók 0 értékre inicializáltak. (Ide ennyi elég.)

Itt a vége. Ha eddig nem tudtad volna elolvasni a kódomat (amit azért nem hiszek), akkor máris menni fog.

Nem feledvén, hogy a programod jóval többet elvégez...

#include <stdio.h>
#include <string.h>

main()
{
    int L=0,L1=0;
    char Buffer[0x100];
    while ( gets(Buffer) )
    {
        L1=strlen(Buffer);
        if ( L1 > L )
            L=L1;
    }
}

Ez kb. az awk programnak megfelelő valami. Ebből vagy az awk-ból előállitva a programodat sokkal szebb lesz, és kb. ugyanannyi sorral kell kiegészíteni.. ;)

Az látszik, hogy a feladatot megoldva mindkét nyelven rövid a fejlesztési idő és hasonló az eredmény.

Bár nem ismerem a PowerMac-et, de mondjuk győztél. ;(

Kösz az awk gyorstalpalót. Majd lehet, hogy egyszer jobban belemélyedek.

A mellékelt C programmal csak egy baj van, hogy a gets() a stdin-ről olvas be, azaz ahhoz, hogy ez a program bármit csináljon be kell pipe-olni neki az ASCII-artot, tehát nem standalone; ahhoz akkor fgets() kell, tehát a fájlkezelést nem lehet megúszni. Az előre a veremben allokált buffer persze a malloc()-ot feleslegessé teszi, de hát csak gyorsan elővettem a saját fájlberántómat...

Szóval, ha kiegészítjük a C példád, hogy a paramétereket is lekezelje, meg ne csak a hosszát nézze a sornak, hanem copyzza ki, amit kell (BTW copy, írtad a substr() függvényt; na az C-ben nincs, csak C++-ban: a string ősosztály egyik metódusa, meg amúgy is, azzal nem sokra megyünk, mert a terminál bal oldalán kell "lebegtetni" a kicopyzott oszlopokat), akkor ezt kapjuk:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>

#define SZ_LINEBUF 4096

int main(int argc, char *argv[])
{
	FILE *f;
	char buf[SZ_LINEBUF], col, row, start, stop, c;
	bool copy;

	if (argc != 4)
	{
		fprintf(stderr, "Usage: getcols <file> <start> <stop>\n");
		return 0;
	}
	start = strtol(argv[2], NULL, 10);
	if (errno != 0)
	{
		fprintf(stderr, "Erroneous start column: %s\n", argv[2]);
		return 3;
	}
	stop = strtol(argv[3], NULL, 10);
	if (errno != 0)
	{
		fprintf(stderr, "Erroneous stop column: %s\n", argv[3]);
		return 4;
	}
	f = fopen(argv[1], "rb");
	if (f == NULL)
	{
		fprintf(stderr, "Cannot open file: %s\n", argv[1]);
		return 1;
	}
	row = 0;
	while (!feof(f))
	{
		fgets(buf, SZ_LINEBUF, f);
		col = 0;
		copy = false;
		while ((c = buf[col]) != 0)
		{
			if ((col == start) && (!copy))
			{
				copy = true;
			}
			if (copy)
			{
				fprintf(stdout, "\033[%d;%dH%c", row, col - start, c);
			}
			if ((col == stop) && (copy))
			{
				copy = false;
			}
			++col;
		}
		++row;
	}
	fclose(f);
}

Ez 60 sor a másik felállás 100 sora helyett, szóval az "Ezzel 2/3 program ki is van dobva." az inkább jó 1/3-nak (pontosabban: 40%-nak). :P A bináris mérete is 10k-ról 6k-ra ment össze. Amitől megszabadultunk: a berántós függvény, a malloc() és lekezelése, valamint a sortörések külön kezelése (fgets() itt intézi helyettünk); szóval annyira azért nem volt bloated az a C program, amit adtam. :P
Sebességben kb. semmi különbség nincs a kettő között; pár tízezreléknyi különbséget lehet csak mérni az egyben beolvasós javára:

root@Csabi:~# time for i in {1..50000}; do getcols_b asciiart.txt 30 60 >/dev/null; done

real    2m0,616s
user    0m3,072s
sys     0m31,368s
root@Csabi:~# time for i in {1..50000}; do getcols asciiart.txt 30 60 >/dev/null; done

real    2m0,598s
user    0m2,956s
sys     0m31,396s

De gondolom ez az fs-cache miatt van így. Valami retroplatformon biztos nagyobb lenne a különbség. :P

A mellékelt C programmal csak egy baj van, hogy a gets() a stdin-ről olvas be, azaz ahhoz, hogy ez a program bármit csináljon be kell pipe-olni neki az ASCII-artot, tehát nem standalone

Aha. Ilyenkor ezt szoktam ajánlani. ;)

program < asciiart.txt

Az a "visszakacsacsőr" olyat tesz, mintha a program ezt csinálta volna: openat(STDIN_FILENO,"asciiart.txt",O_RDONLY)

Tehát itt egy darab pipeline sincs. Ezt átirányításnak hívják.

Mivel csak a "program" és a shell vesz részt a Nagy Műveletben, ezért "standalone". (WTF standalone?)

Valójában a következő történik:

keyboard > (STDIN_FILENO) shell ((STDOUT_FILENO) ---> buffer > screen ; (STDERR_FILENO) > screen)
- program indításakor
keyboard > (STDIN_FILENO) program ((STDOUT_FILENO) ---> buffer > screen ; (STDERR_FILENO) > screen)
- átirányításkor
asciiart.txt > (STDIN_FILENO) program ((STDOUT_FILENO) ---> buffer > screen ; (STDERR_FILENO) > screen)

Persze a keyboard és a file elemhez is tarozik buffer, pontosan akkora, amekkora őket megilleti. Hacsak mást nem mondasz nekik. ;)

A shell és a program is ugyanazokat a fd-ket használja, de ez még viszonylag egyszerű eset. Ha sok program fut, akkor már érdemes úrrá lenni a kavarodáson.

írtad a substr() függvényt; na az C-ben nincs

Ki hitte volna? ;)

De ilyen van:

buffer[j+1]=0;
puts(&buffer[i]);

Ami kb. megfelel a "print substr(buffer,i+1,j-i+1)" awk kifejezésnek.

De gondolom ez az fs-cache miatt van így. Valami retroplatformon biztos nagyobb lenne a különbség. :P

Hááát, ezt 10M alatt nem fogod megtudni. :-D

Az ilyen "írok-olvasok" feldolgozások általában egyenletes sebességgel futnak. Ugyanez több szálon sem problematikus. A feladat akkor kezdődik, ha sok adattal, több szálon, bonyolult feldolgozást és indexelést is kell végezni. Ilyenkor a legfontosabb a feladathoz hangolt diszk alrendszer és az io buffer (per stream) mérete. Jelentős diszk terhelésnél érdemes úgy hangolni a rendszert, hogy a több szál kicsivel hosszabb ideig fusson, mint az egy. ;)

A retroplatform alatt mit értsek? (C64 nem ér!)

> Az a "visszakacsacsőr" olyat tesz, mintha a program ezt csinálta volna: openat(STDIN_FILENO,"asciiart.txt",O_RDONLY)

Jogos, a redirectről megint megfelejtkeztem, de ettől függetlenül ez még mindig egy külső függőség (a shelltől), míg amit én írtam az UNIX-okon kívül is működik, ott is ahol ez az operátor nincs.

> Ki hitte volna? ;)

> De ilyen van:
> buffer[j+1]=0;
> puts(&buffer[i]);

> Ami kb. megfelel a "print substr(buffer,i+1,j-i+1)" awk kifejezésnek.

Volt a mondatnak egy második fele is: "meg amúgy is, azzal nem sokra megyünk, mert a terminál bal oldalán kell "lebegtetni" a kicopyzott oszlopokat".

> Hááát, ezt 10M alatt nem fogod megtudni. :-D

> Az ilyen "írok-olvasok" feldolgozások általában egyenletes sebességgel futnak. Ugyanez több szálon sem problematikus. A feladat akkor kezdődik, ha sok adattal, több szálon, bonyolult feldolgozást és indexelést is kell végezni. Ilyenkor a legfontosabb a feladathoz hangolt diszk alrendszer és az io buffer (per stream) mérete. Jelentős diszk terhelésnél érdemes úgy hangolni a rendszert, hogy a több szál kicsivel hosszabb ideig fusson, mint az egy. ;)

Tekintve, hogy itt az egész file valószínűleg belefér egy szektorba, így kb. maradhatunk annál, hogy elméletileg gyorsabb az egyszerre beolvasós, gyakorlatilag meg kb. nem az. Illetve elhanyagolhatóan pici mértékben.

> A retroplatform alatt mit értsek? (C64 nem ér!)

Dehogy, nem arra gondoltam, hanem annál azért erősebb gépre, pl. Amiga, vagy 286-os PC.

de ettől függetlenül ez még mindig egy külső függőség (a shelltől), míg amit én írtam az UNIX-okon kívül is működik, ott is ahol ez az operátor nincs.

Mottó: Ha valaki a szilikonmellével dicsekszik, az olyan, mintha azzal dicsekedne, hogy hibátlanul elfingotta a Für Elise-t. :-)

- Ha ez megnyugtat, PIC18 platformon (8 bit) 64MHz-es órajellel is megcsinálom gyorsabban.

- Nem *nix-ra néhány abszolút üdítő kivétellel nem írtam programot már 25 éve. Windowsra is cygwin alatt írok. ;)

Tekintve, hogy itt az egész file valószínűleg belefér egy szektorba, így kb. maradhatunk annál, hogy elméletileg gyorsabb az egyszerre beolvasós, gyakorlatilag meg kb. nem az. Illetve elhanyagolhatóan pici mértékben.

Ha ez egy "versenyprogram" vagy benchmark akar lenni...annak elég picinke.

Viszont a teljes rendszer ismerete nélkül még egy ilyen kis feladatról sem lehet semmit biztosan állítani.  Példa:

Az asciiart.txt 2626 bájt hosszú. Lehet olyan fs alattad, ahol - ennél a méretnél még - az inode tárolja a dir+file elemet. De olyan is, ahol nem, ráadásul 1k logikai blokkmérettel (itt legyen 2 szektor) dolgozik. Az első 1, a második 4 fs műveletet igényel. Közben meg ott csücsül a világ leglassabb entitása a printf(). A második esetben már jobban jársz, ha nem várod ki a teljes beolvasást! Ennek ellenére majdnem mindegy, mert a cache tényleg mindent visz.

Fokozzuk egy kicsit a feszültséget! Van egy rendszerem, amely >80M processzt futtat naponta. A CPU upgrade előtt olyan nagy volt a terhelés, hogy az 5 perc periódusidőt meg kellett növelni, hogy a kiosztott feladatok le tudjanak futni. Utána annyira fürge lett, hogy az 5 perc lejárta előtt végezhet a feladatokkal, aztán pihen egy kicsit. Ok, tehát gyorsabb. És mi van akkor, ha ez a pici program pont az 5 perces periódus elején fut, amikor csúcsterhelés van? (A rendszer diszket is használ! ;)) Na, ilyenkor meg akár a cache is kiürülhet két olvasás között.

A "semmi" futásidejű programokat nem lehet shellben for ciklussal megmérni, mert a process indítás, a script futásidő, stb. akármennyi is lehet. Majdnem azt írtam, hogy szór, de inkább laza a korreláció. ;)

Fogtam a parancsot és csináltan egy N sor hosszú scriptet, azaz lineáris végrehajtást. Ugyanazon a rendszeren a futtató shell függvényében (csh, bash, ksh) 1:2 arányban változhat a végrehajtási idő.

Másik rendszeren a két program futásideje 1:1,1 arányú volt, lineárisan meg 1,1:1.

Még a user és system idők változását és arányait magyarázni sem nagyon lehet, nemhogy megérteni!

Nem is csoda, mert a "real    0m0.002s" értéket a méréstechnikában úgy hívják: zaj. :-D

Ha sok zajt összeadsz, akkor is zajt kapsz.

Szóval ilyet beépített hardver timerrel lehet mérni, ami <1us felbontást tesz lehetővé. A POWER architektúra rendelkezik ilyennel, de mintha 15 éve a linux/Intel viszonylatban olvastam volna hasonlót, bár azt nem használtam. Ezzel az eszközzel olyat is meg tudtam mérni, hogy a memóriába töltött adatbázis három tábájának átlagos lekérdezési ideje 120, 160 és 220ns.

Ennél a kis C programnál a mérési pontokat a main() elején és végén kell elhelyezni. A kapott eredményhez már hozzá lehet adni a programindítás idejét. Hasonlóan mérhető hardverrel is, ha van egy bit gpio. A mérés elején bebillented a bitet, a végén vissza. A jelenséget 1500 forintos logikai analizátorral pontosan meg tudood mérni, esetleg szkóppal.

Retro gépeim: Atlas 604 (100MHz PowerPc 604, 1996), Alix 1D (AMD Geode LX800), bookpc (VIA C3 1GHz), p615 (POWER 4+ 1GHz, 2004)

Ebből most mértem egyet az Alix/FreeBSD 10.3 konfiguráción: 10m40s, tehát a mérésednél 5x lassabb.

Ja, és folyamatosan értem mi a feladat, de asszem már régen túlnőttünk rajta. ;)

> Mottó: Ha valaki a szilikonmellével dicsekszik, az olyan, mintha azzal dicsekedne, hogy hibátlanul elfingotta a Für Elise-t. :-)

Másik mottó: Jobb későn, mint sárgadinnye.

> - Ha ez megnyugtat, PIC18 platformon (8 bit) 64MHz-es órajellel is megcsinálom gyorsabban.

Minél gyorsabban? Az asztali gépemen futó programnál? Vagy ha átírom C64-re, annál?

> - Nem *nix-ra néhány abszolút üdítő kivétellel nem írtam programot már 25 éve. Windowsra is cygwin alatt írok. ;)

És? Én meg próbálom úgy megírni mindenemet, hogy minél hordozhatóbb legyen.

> Ha ez egy "versenyprogram" vagy benchmark akar lenni...annak elég picinke.

Mármint hogy mi? A topicnyitót kérdezd, hogy mi ez, én csak egy lehetséges megoldást adtam neki.

> Viszont a teljes rendszer ismerete nélkül még egy ilyen kis feladatról sem lehet semmit biztosan állítani. ... Ennek ellenére majdnem mindegy, mert a cache tényleg mindent visz.

...

> A "semmi" futásidejű programokat nem lehet shellben for ciklussal megmérni, mert a process indítás, a script futásidő, stb. akármennyi is lehet. Majdnem azt írtam, hogy szór, de inkább laza a korreláció. ;)

Ezért kell baromi sokszor megmérni: egy bizonyos mérésszám felett kijön a különbség a két program között, ugyanis a többi, ami azonos, ha adott pillanatban külső behatások (clock, load, stb.) miatt más időt is ad, sok mérés után az összesítés konvergálni fog a valódihoz.

> Nem is csoda, mert a "real 0m0.002s" értéket a méréstechnikában úgy hívják: zaj. :-D

> Ha sok zajt összeadsz, akkor is zajt kapsz.

És a különbség lesz az, ami nem zaj.

> Szóval ilyet beépített hardver timerrel lehet mérni, ami <1us felbontást tesz lehetővé. A POWER architektúra rendelkezik ilyennel, de mintha 15 éve a linux/Intel viszonylatban olvastam volna hasonlót, bár azt nem használtam. Ezzel az eszközzel olyat is meg tudtam mérni, hogy a memóriába töltött adatbázis három tábájának átlagos lekérdezési ideje 120, 160 és 220ns.

> Ennél a kis C programnál a mérési pontokat a main() elején és végén kell elhelyezni. A kapott eredményhez már hozzá lehet adni a programindítás idejét. Hasonlóan mérhető hardverrel is, ha van egy bit gpio. A mérés elején bebillented a bitet, a végén vissza. A jelenséget 1500 forintos logikai analizátorral pontosan meg tudood mérni, esetleg szkóppal.

Egy mai rendszeren, SMP, multitaszking és változó órajel van, még a programon belül mérve is kaphatsz eltérő eredményeket. Nem a hardveres timer miatt, hanem mert nem biztos, hogy mindig ugyanannyi idő lesz, amíg lefut a program. Épp nem kapott időt, épp lejjebb volt az órajel, whatever.

> Ebből most mértem egyet az Alix/FreeBSD 10.3 konfiguráción: 10m40s, tehát a mérésednél 5x lassabb.

És melyiket mérted meg? Az egybe behúzóst, vagy a soronkénti olvasóst?

> Ja, és folyamatosan értem mi a feladat, de asszem már régen túlnőttünk rajta. ;)

Hát nem gyengén.