Shell (awk, sed, "bármi") kérdés: listából sql-t

Fórumok

Adott egy lista:

Adott egy lista, amiben több ezer gép mentéséről van információ, ilyen formában:

1716226178           dbs01
1716232046           dbs01
1716312666           dbs01
1716318416           dbs01
1716398846           dbs01
1716404845           dbs01
1716485330           dbs01
1716491210           dbs01
1715965107       vcenter01 web99
1716051341       vcenter01 web99
1716137815       vcenter01 web99
1716224247       vcenter01 web99
1716310498       vcenter01 web99
1716396981       vcenter01 web99
1716483542       vcenter01 web99

Azaz időbélyeg, a vcenter neve ha VM, és a host neve. Az oszlopok sorrendjén tudok változtatni.

Ebből kellene ilyet csinálni:

UPDATE servers set backup='1716491210' WHERE hostname='dbs01';
UPDATE servers set backup='1716483542' WHERE hostname='web99';

Azaz frissíteni a táblát az utolsó mentés idejével.

Ami jelenleg van:

while read HostName
 do
  BTIMESTAMP=$(awk -v HNAME="${HostName}" 'BEGIN{C=0} {if ($NF == HNAME && $1>0+C) C=$1} END{print C}' "${CACHEFILE}")

 if ! [ -z ${BTIMESTAMP} ];
 then
   echo "UPDATE servers set backup='$BTIMESTAMP' WHERE hostname='$HostName';"  >> "${SQLFILE}"
 fi
done < "${SERVERNAMES}"

az nem tetszik, mert gépenként végigmegy a listán, meg egyáltalán, nem elegáns, jó lenne egyetlen awk paranccsal megoldani.

Nem őrület fontos, már így is vagy ezerszer gyorsabb, mint amit megkaptam, hogy írjam át.

A probléma adott, nem tudok pl. olyan listát gyártani, ami csak az utolsó mentéseket tartalmazza. Persze lehet egy lépes az, ami ezt leválogatja a meglévőből.

Amiből lehet válogatni, az az, ami a "szokásos" Centos 7 (nemsokára RHEL9) telepítésben rendelkezésre áll.

Előre is köszönöm az ötleteket :)

Hozzászólások

ilyen feladatokat siman megold a ChatGPT, just sayin'...

Az adott problémát valóban meg lehet oldani egyetlen awk paranccsal. Az awk parancs segítségével egyszerre beolvashatjuk a fájlt és előállíthatjuk a kívánt SQL parancsokat. Az alábbi megoldás az awk parancsban végzi el az összes szükséges műveletet:


 

bash


 

awk ' { if ($2 == "dbs01" || ($2 == "vcenter01" && $3 == "web99")) { if ($1 > backup[$2 " " $3]) { backup[$2 " " $3] = $1 } } } END { for (h in backup) { split(h, arr, " ") hostname = (arr[2] == "dbs01") ? arr[2] : arr[3] print "UPDATE servers set backup='\''" backup[h] "'\'' WHERE hostname='\''" hostname "'\'';" } }' input.txt > updates.sql

Az input.txt tartalmazza az eredeti adatokat. Az awk parancs végrehajtása után az updates.sql fájl tartalmazni fogja az összes szükséges SQL frissítést.

Működés részletesen:

  1. Beolvasás: Az awk soronként olvassa be a fájlt.
  2. Feltétel ellenőrzése: Ellenőrzi, hogy az aktuális sor dbs01 vagy web99 tartalmaz-e.
  3. Legnagyobb időbélyeg tárolása: Ha az aktuális sor időbélyege nagyobb, mint az előzőleg tárolt időbélyeg az adott hosztnévhez, akkor frissíti az időbélyeget.
  4. SQL parancsok generálása: Az END blokkban létrehozza az SQL frissítési parancsokat.

Használat:

  1. Másold be a fenti awk parancsot egy shell scriptbe vagy futtasd közvetlenül a terminálban.
  2. Az input.txt fájl tartalmazza a bemeneti adatokat az eredeti formában.
  3. A kimeneti fájl (updates.sql) tartalmazni fogja az SQL frissítési parancsokat.

Ez a megoldás egyszerűbb és hatékonyabb, mivel az awk segítségével egy lépésben megoldja a feladatot, és nem szükséges több iterációt futtatni a lista feldolgozásához.

4 és fél éve csak vim-et használok. elsősorban azért, mert még nem jöttem rá, hogy kell kilépni belőle.

vissza lehet kérdezni tőle. nekem csak kíváncsiságból megcsinált kb 10 perc alatt egy olyan feladatot, amin pár éve két napot otthon elbütyköltem (persze hobbi projekt, és közben azért folyt a sör, na de mégis)

4 és fél éve csak vim-et használok. elsősorban azért, mert még nem jöttem rá, hogy kell kilépni belőle.

Mi a pontos specifikáció? Első és utolsó oszlopot nézzük (ha van közbülső, az nem érdekes)? Minden gépnévre az utolsó őt tartalmazó sor? Vagy a legnagyobb időcímkéjű? Vagy mindegy?

Én személyes véleményem:

awk-t már rég elfelejtettem, nyakatekert és számomra természetellenes szintaxis egy roppant limitált funkcionalitásért. Szerintem: ha shell szkriptben egyszerűen megoldható (nagyjából natívan, tehát nem awk-t hivogatva), akkor az; ha nem, akkor python.

Legyen egy asszociatív tömböd / szótárad / ki minek hívja, kulcs a gépnév, érték az időcímke. Ciklusban olvasod a sorokat, minden sorra azt parse-olod (szóközök mentén robbantva, stb.), majd bekorad az értéket a tömbbe. Ha ezzel megvagy, akkor újabb ciklus a tömb összes elemét kiírja a megfelelő szintaxisban.

Bónusz: Python asszem 3.6-tól kezdve a dictionary megjegyzi a beszúrási sorrendet, ebben a sorrendben fogsz végiglépni rajta amikor kiírod az értékeket.

$ sqlite3
sqlite> .mode csv
sqlite> .separator "\t"
sqlite> CREATE TABLE "servers"("backup", "vm", "hostname");
sqlite> .import lista.txt servers
sqlite> UPDATE servers set backup='1716491210' WHERE hostname='dbs01'; UPDATE servers set backup='1716483542' WHERE hostname='web99';
sqlite> .once lista.txt
sqlite> SELECT * FROM servers;
sqlite> .quit

Hmm, ma is tanultam valamit, szóval tudom állítani az elválasztókaraktert.

Viszont ez azt is jelenti, hogy esélyes, hogy jobb lenne az egészet betolni a céladatbázisba egy ideiglenes táblába, és abból frissíteni a szerverek adatait.

Szóval a probléma átalakul sql kérdéssé, ezt viszont még át kell gondolnom.

A nyers lista 18k sor, és a végén csak 746 szervert kell frissíteni. Az arányok kb. ilyesmik lesznek később is, szóval a kérdés, hogy melyik tetszik jobban:

  • 18k buta insert és egy ügyes select+update
  • preprocesszálás és ~huszadannyi update

Egyelőre a második tűnik elegánsabbnak.

 

Ejj, nem kellett volna kapkodni a válasszal.

Szóval eredetileg úgy van, hogy egy ilyen reportot kérek:

-r "nsavetime,client,vmname"

Ebból az lesz, hogy ha VM, akkor a második oszlop a vcenter neve, és utolsó/harmadik oszlop a VM neve. Ha fizikai, akkor a második oszlop a kliens/gép neve. Szóval így mindig az utolsó oszlop a az érdekes. Meg persze az első.

Ha állítok elválasztókaraktert, akkor ez bonyolódik, mert vagy a második, vagy a harmadik.

Viszont az még egy vállalható kompromisszum lenne, ha két listát generálnék, egyet a VM-ekről, egyet meg a fizikai gépekről, és összefűzném őket.

Akkor ilyen lenne:


1716226178        dbs01
1716232046        dbs01
1716312666        dbs01
1716318416        dbs01
1716398846        dbs01
1716404845        dbs01
1716485330        dbs01
1716491210        dbs01
1715965107        web99
1716051341        web99
1716137815        web99
1716224247        web99
1716310498        web99
1716396981        web99
1716483542        web99

Vagy akár veszővel (viszonylag bármilyen karakter lehet) elválasztott.

Én 3x elolvastam, de nem értem hogy mi a feladat és mi a probléma...

Ez a lista eleve SQL-ből jön, nem? Miért kell kiszedni, és legyártani a queryket, amit majd visszatolsz? Miért nem direktben az SQL-ben?

"Sose a gép a hülye."

Én nem sz@roznék azzal, hogy a max(timestamp) keresgélése - betolni mindet egy táblába, és onnanmár egy query kiszedi neked a hostonkénti max(timestamp) értékeket. a vcenter nevét én vagy olyan oszlopba raknám, ami default null, vagy simán két oszlopra konvertálnám az adatokat: a vcenter és a host adatokat összeraknám egy oszlopba, mondjuk egy "#" vagy "|" vagy olyan karakterrel elválasztva, ami sem a vcenter, sem a host mezőben nem szerepelhet. De lehet akár host@vcenter is... Illetve uniq constraint az időbélyeg-host (vagy host@vcenter) párosokra, hogy később futtatott mminfo kimenetből értelmesen tudd tölteni a táblát (ami már benne van, az ne menjen bele fölöslegesen újra). 

A DB-d mérete még 123456 sor esetén is lófütty lesz - és nem kell a betöltéskor sajtreszelőzni azon, hogy mi az új/legutolsó adat...

Nade a cél a hatékonyság lenne.

A nyers adat ~20x annyi, mint a hasznos. Az adatokat be lehet tölteni egy temp táblába, amit utána el is lehet dobni.

Hacsak ezt nem lehet nagyon optimalizáltan megoldani (sík hülye vagyok az sql-hez, illetve a mysql-hez, nem titok, ez a db motorja), mert rémlik olyan, hogy a soronkénti insertnél kb. csak jobb megoldások vannak, akkor ez több kakaót fog elvinni, mint az update (amin valszeg megint lehetne okosítani, hogy ne 700 update legyen, hanem csak egy).

A többi részét nem igazán értem, a vcenter nem kell, de az mminfo olyan, hogy a kliens neve a vcenter neve, ha vmware mentésről van szó. De ez apróság, az utolsó oszlop mindig az, ami nekem kell.

 

Update: közben rájöttem, miért nem jó az insert. A mentéseket két hétre visszamenőleg kérdezem le (tudom, ne kérdezzem két hétre, de nem minden gépről készül mentés minden nap, van, amiről csak heti, havi egy, és nem lenne jó, ha minden backup policy váltáskor matatni kéne ezt a scriptet is). Nade ha egy gépet decommolunk, akkor az egyik lépés az az, hogy el kell távolítani ebből a listából. Ha ez még automata is lesz, pl. a vcenter alapján, akkor a script visszateszi, mert két hétig mindig lesz egy utolsó mentése.

Ha fix a szerkezet akkor ez is jó lehet ha csak awk játszik:

 

#!/usr/bin/awk -f
# Az utolso 24 oraban elkeszult mentesek idopecsetjet frissiti, ertelemszeruen modosithato
BEGIN{
        yesterday = sysdate() - 3600 * 24
}
$1 >= yesterday {
        if ($2 == "vcenter01") print "UPDATE servers set backup=\047" $1 "\047 WHERE hostname=\047" $3 "\047;";
        else print "UPDATE servers set backup=\047" $1 "\047 WHERE hostname=\047" $2 "\047;"
}
 

De ha az bejön lecserélheted a printet system()-re és akkor egyből végre is hajtja. 

Érdekességképp itt van, amit nekem dobott, és ez tetszik is, alighanem erre megyek majd:

 

You can replace the loop with a single awk command to streamline the process. Here is an approach to achieve that:

  1. Read the HostName values into an array.
  2. Use awk to process the CACHEFILE for all hostnames at once.
  3. Use awk to generate the SQL update statements.

Here's how you can do it:

awk -v CACHEFILE="${CACHEFILE}" '
  BEGIN {
    # Load hostnames into an array
    while ((getline hostname < "hostnames.txt") > 0) {
      hostnames[hostname]
    }
    close("hostnames.txt")
  }
  {
    # Process each line of CACHEFILE
    if ($NF in hostnames) {
      if ($1 > timestamps[$NF]) {
        timestamps[$NF] = $1
      }
    }
  }
  END {
    for (hostname in timestamps) {
      if (timestamps[hostname] > 0) {
        printf "UPDATE servers set backup=\"%s\" WHERE hostname=\"%s\";\n", timestamps[hostname], hostname >> "'${SQLFILE}'"
      }
    }
  }
' "${CACHEFILE}"

Explanation:

  1. BEGIN Block:

    • Read the hostnames from a file (hostnames.txt), where each line contains a HostName. Store these hostnames in an array.
  2. Main Block:

    • For each line of the CACHEFILE, check if the last field ($NF) is in the array of hostnames.
    • If it is, and if the first field ($1) is greater than the current maximum timestamp for that hostname, update the timestamp for that hostname.
  3. END Block:

    • For each hostname, if there is a valid timestamp, print the SQL update statement to the SQLFILE.

Replace "hostnames.txt" with the path to your file containing the list of hostnames. This approach ensures that you process all hostnames and generate the SQL update statements in one go, avoiding the need for a loop.

Ez igy eleg gyasz. Ha ennyit tud az LLM, akkor meg a hobbista szintemet sem veszelyezteti :)

Ennyi kell (nem shelyes szintaktikailag):

BEGIN {

%%beallitod legalabb a field separatort, ha mast nem is%%

}

{

hostname = %%valamelyik oszlop, ezt talald ki%%

timestamp = %%masik oszlop%%

if (hostname in myarray) {

  if (timestamp > myarray[hostname]) myarray[hostname] = timestamp

} else {

  myarray[hostname] = timestamp

}

}

END {

%%kiirod, ahogy akarod%%
%%for (hostname in myarray) print hostname, myarray[hostname]

}

Lehet, hogy lehet optimalizalni, de nem tudom fejbol a nem letezo tombelem viselkedeset.

Az AWK tombje asszociativ. Folosleges extra koroket futni miatta. Persze, ha tobb hostneved van, mint egen a csillag, akkor kifuthatsz a memoriabol. De akkora halozathoz fizessenek meg hozzaertobb szakembert, mint en :)

Szerkesztve: 2024. 05. 29., sze – 12:16

Esetleg ez egy "vmi" nevű fájlba:

{ printf ("UPDATE servers set backup='%s' WHERE hostname='%s'\n", $1, $NF )}
 

Majd

awk -f vmi input.txt

 

Ja, hogy az utolsó mentés... ok, azt nem láttam.

 SELECT hostname, MAX(timestamp) AS max_timestamp
    FROM mytable
    GROUP BY hostname

Kiválasztja a legutolsó mentés sorát a hostname groupban, 
a HOSTNAME egyedi ,de lehet bontani,  ha jól értem a dolgot .

nyers lista 18k sor, és a végén csak 746 
 

kapsz egy 746 soros listát.

Lehet, hogy csak nekem kérdéses, de ehhez minek AWK?

Meg lehet oldani extra szintaxis nélkül is, csak tr + cut kell hozzá.

Én több részre szedném a feladatot.

Időnként jön egy lista, ami pár sorral több mint az előző.

Mivel a (már) felesleges sorok teljesen azonosak az előző állományban lévővel, egy diff pillanatok alatt kidobja a zaj nélküli állományt.

A zaj nélküli állományon pedig már nem ügy végigmenni, hiszen minden sora kelleni fog.

Szerkesztve: 2024. 05. 29., sze – 14:08

Hát akkor lépésről lépésre. A jelenlegi listából összegyűjteni azt a pár százat, az 21 karakter awk-ban (igazából 19, mert nem kell a feltétel köré a zárójel):

awk '( $1 > m[$NF] ) {m[$NF]=$1 } '

Itt kihasználod hogy asszociatívak a tömbök, tehát a mindenkori utolsó mező ( awk-nyelven $NF ) pont jó lesz indexnek, és eltárolod értékként az időbélyeget; valamint kihasználod, hogy nem létező változó értéke az üres sztring, ami aritmetikai műveletben nulla értékű. Így ha még nem volt a gép, akkor 0 lesz a (nem-létező) tömbelem értéke, aminél kb bármilyen timestamp nagyobb. Ha meg már volt ilyen gép, akkor amelyik ts az újabb, az tárolódik.

Hátramarad a DB-nek szóló parancs, ez sokkal hosszabb, és sokat kell szarozni az aposztrófokkal és idézőjelekkel. Itt és most az egyszerűség kedvéért csak kiírom az általad adott UPDATE parancsot gépenként, erre meg pont jó az END mintában egy for ciklus. Azaz fentihez még hozzá kell rakni:

'END { for ( i in m ) { print "UPDATE servers set backup=" m[i] " WHERE hostname=" i " ; " }'

És ezt megeteted azzal az SQL-t tudó akármivel, azaz:

awk '$1 > m[ $NF ] { m[ $NF ] = $1 }

END { for ( i in m ) { print "UPDATE servers set backup=" m[ i ] " WHERE hostname=" i " ; " }' < szövegfájl_nevekkel_és_ts_sel | SQL_parancs

Ha az SQL parancsban a

set backup='xyz' WHERE hostname='ABC'

részbe tényleg kellenek az aposztrófok (márpedig valszeg igen), akkor azt parancssorban beírni nagyon kacifántos lesz. Igazság szerint baromi sokat nyersz az olvashatósággal, ha egy awk-parancsfájlba írod (mert ott legalább a shell elől nem kell takarni a dolgokat), és awk -f fnév.txt  formában indítod.

$ cat fnév.txt

$1 > m[ $NF ] { m[ $NF ] = $1 }

END { for ( i in m ) { print "UPDATE servers set backup='" m[ i ] "' WHERE hostname='" i "' ; " }

 

Ha jobban megnézed m[ i ] előtt van egy aposztróf-idézőjel, a WHERE előtt egy idézőjel-aposztróf, a hostname után pedig megint egy aposztróf-idézőjel, és i után megint egy idézőjel-aposztróf sorozat. (És ezt most képzeld el shellben begépelni :-) )

Aztán ha új szerver érkezik a mentésbe, akkor annak az adata megy a levesbe az update where feltétele miatt...

Mondjuk MySQL esetén a host oszlopra uniq constraint, és az update helyett valami ilyen insert:

"INSERT INTO mentesek (host, mikor) VALUES ('"i"','"m[i]"') ON DUPLICATE KEY UPDATE mikor='"m[i]"';"

Igen, ki lehet találni még ilyen "ha" dolgokat.

Viszont a tábla rendszeresen frissül a vcenterből. Is. Meg máshonnét is.

Ennek ellenére ez nem annyira rossz ötlet, mint elsőre gondoltam.

 

Azt hiszem ezt ide akartam inkább:

Update: közben rájöttem, miért nem jó az insert. A mentéseket két hétre visszamenőleg kérdezem le (tudom, ne kérdezzem két hétre, de nem minden gépről készül mentés minden nap, van, amiről csak heti, havi egy, és nem lenne jó, ha minden backup policy váltáskor matatni kéne ezt a scriptet is). Nade ha egy gépet decommolunk, akkor az egyik lépés az az, hogy el kell távolítani ebből a listából. Ha ez még automata is lesz, pl. a vcenter alapján, akkor a script visszateszi, mert két hétig mindig lesz egy utolsó mentése.

ha van egy gép, aminek a neve abcd, és nem kerül bele ebbe a táblába, de van róla mentés, akkor az update ... where gepnev='abcd' egészen pontosan nulla sort fog update-elni, azaz amíg máshonnan nem kerül egy insert into -val a táblába a gép neve, addig a mentést sem fogja benne rögzíteni semmi.

Neked a hostneveket és az utolsó mentés dátuma adatokat is merge-elned kell, ami a már ismert, db-ben lévő host-mentés ideje párosoknak a megfelelő összefésülését jelenti a mentőrendszerből lehúzott listával, amit célszerű berakni egy átmeneti táblába, mert sql-ben sokkal egyszerűbb összefésülni a két adathalmazt.

"ha van egy gép, aminek a neve abcd, és nem kerül bele ebbe a táblába, de van róla mentés, akkor az update ... where gepnev='abcd' egészen pontosan nulla sort fog update-elni, azaz amíg máshonnan nem kerül egy insert into -val a táblába a gép neve, addig a mentést sem fogja benne rögzíteni semmi"

Igen. Ez az elvárt működés.

Hint: \047 Most nem nyitok megint vpn-t, de egész kényelmesen lehet így aposztrófokat írni.

De ha már itt vagyok:

END{ for ( i in m ) print "UPDATE servers set backup=\047" m[i] "\047 WHERE hostname=\047" i "\047 ; " }' < "${CACHEFILE}" > "${SQLFILE}"

Csak úgy viccből:

#!/bin/bash

declare -A hosts
for kv in $(awk '{ print $1"="$2"\n" }' <(cat indata.txt |  tr -d '\r' | sed -e 's/vcenter..//g' | sort -n | grep -Ev "^$" )); do
	t="${kv%%=*}"; h="${kv##*=}"
	if [ -z "${hosts[$h]}" ]; then hosts[$h]="$t"
	else 
		if [ ${hosts[$h]} -lt $t ]; then hosts[$h]="$t"; fi
	fi
done

for h in "${!hosts[@]}"; do echo "UPDATE servers set backup='${hosts[$h]}' WHERE hostname='$h';"; done

 

Nagy file esetén extra kávé ivásra ad ürügyet, illetve a századik vcenter után törni fog, kivéve ha hexadec az indexelés.

Ó, nem, az előző kolléga úgy oldotta meg, hogy a script minden géppel átballagott ssh-val a management gépre, ott lekérte az adott gépre vonatkozó backupokat (két hétre visszamenőleg), aztán az sort, tail, cut, újra.

Ehhez képest az én megoldásom, hogy egyszer lekéri az összeset, majd abból ügyeskedik, helyből hozott tényleg kb. ezerszeres gyorsulást, és őszintén szólva itt már nekem nem érte meg, hogy tovább matassak vele, viszont idegelt, mert biztos voltam benne, hogy van valami sokkal elegánsabb megoldás is.

Valószínűleg messze ült a kávégéptől, és el kellett látnia a gépet munkával, hogy legyen ideje kávézni.

Ilyenkor az ember muszáj, hogy felesleges ssh -val duzzassza fel a kódot, mert a sleep kislányoknak való.

Nekem hobbim, hogy melóban ilyen bash/awk csatakígyókat rakok össze, mondjuk a fenti nem valami szép, ez csak ilyen első draft. Azt a sed -es vcenter kiszedést egyenesen szégyellem, de egyszerűbb volt begépelni, mint elgondolkodni más módszeren.

Szerintem Zahy awk -s megoldása a legjobb, az egy mestermunka, egyedül csupán abba tudnék belekötni, hogy túl jó. Ezalatt azt értem, hogy annyira jó, hogy már én sem értem, legalábbis eltöprengenék rajta ha komment nélkül kerülne elém, hogy na ez most mi a csudát akar csinálni, mert nem látom benne, hogy hol különbözteti meg a két illetve három részből álló sorokat, és hogy hogy jön ki az állományból a host neve és az időpont. :-)

Én úgy vélem, hogy a scriptek terén a legjobb megoldás az, amit a padtársam is ért, hogy ha hozzá kell nyúlnia, akkor ne kérdezzen bele, mert akkor kiderül, hogy már rég elfelejtettem, hogy azt miért pont úgy írtam. :-)

Én úgy vélem, hogy a scriptek terén a legjobb megoldás az, amit a padtársam is ért, hogy ha hozzá kell nyúlnia, akkor ne kérdezzen bele, mert akkor kiderül, hogy már rég elfelejtettem, hogy azt miért pont úgy írtam. :-)

Itt jon elo a Python olvashatosaga. Az meg, hogy par millisec-cel lassabb (bar awk-hoz kepest lehet, hogy az sem) meg kb. mindegy erre a felhasznalasra. Ez mindenben megirhato, amiben van filekezeles meg asszociativ tomb, es a kod is nagyjabol azonos lesz: beolvasod soronkent, ha ujabb a timestamp a letaroltnal, vagy uj a host, akkor eltarolod, a file vegen meg kiirod az egeszet.

Lehet meg cizellalni, de kb. ennyi:

#!/usr/bin/env python3

import sys

if len(sys.argv) < 2:
    print(f"Usage e.g.: {sys.argv[0]} input.txt")

for filename in sys.argv[1:]:
    newest_backup_time = {}

    with open(filename, "r") as input_file:
        for row in input_file:
            row_elements = row.split()
            timestamp, hostname = row_elements[0], row_elements[-1]
            if hostname not in newest_backup_time or newest_backup_time[hostname] < timestamp:
                newest_backup_time[hostname] = timestamp

    for hostname, timestamp in newest_backup_time.items():
        print(f"UPDATE servers set backup='{timestamp}' WHERE hostname='{hostname}';")

Szerintem olvashatobb, mint a perl, awk, meg hasonlo szornyusegek - meg ha nem is ismered a konkret szintaxist, akkor is.
Ha picit hosszabb, lehet clean code-ozni, de itt az egesz program ennyi, nem szorakoznek vele.

A strange game. The only winning move is not to play. How about a nice game of chess?

Pájtonban is láttam botrányosan rondán formázott kódot, és perl-ben is nagyon szépet és jól olvashatót. Attól, hogy a pájtonra azt mondják(!) hogy abban csak szépen lehet kódot elkövetni...Nos az akkora bullshit, mint ide Lacháza...

Ja, és ha véletlenül valaki rosszul kopipaste a kódot, és itt-ott elcsúszkálnak a dolgok, akkor nagyjából kuka az egész...

> nem látom benne, hogy hol különbözteti meg a két illetve három részből álló sorokat

Sehogy. Mind a két esetben az utolsó oszlopban van a hostname, ez a $1, $2, $3, ... mintájára a $NF (NF = number of fields, adott sorbeli oszlopok száma, előtte egy $-ral az annyiadik - azaz - utolsó oszlop a sorban)

 

> nem látom benne,  .... és hogy hogy jön ki az állományból a host neve és az időpont. :-)

Az első oszlop pedig az időpont, azaz amikor az első felében azt mondom: m[ $NF ] = $1, akkor az m (machine) nevű tömb $NF nevű eleme - azaz a gépnév az index - értékül kapja a $1-ben levő timestamp-et. Utána az "END { for ( i in m) ..." ciklus az END miatt a feldolgozás után végigmegy a tömb indexein - azaz a gépneveken - ez van az i változóban valami random sorrendben; és kiírja az m[ i ] és az i értékeket. i értéke a gépnév, m[i] a TS.

Azaz ugyanaz, mint amit a python kódban ír a kolléga, de neki fájlokat kell nyitogatni, meg minden csacskaságot, nekem meg gyárilag megcsinálja az awk :-)

Ahá, így már világos!

Ez a $NF nagy jóság, és ez a példa gyönyörűen mutatja, hogy milyen szép logika építhető vele.

Marha irigy vagyok most rád, lehet, hogy jobban meg kéne tanulnom az awk -t! :-)

Szerintem egy csomó kódomon javítana, mert nekem szokásom az üzleti logikát bash -ban megírni, és az awk -t csak string műveletekre használni, nagyon ritkán írok bele if -et. Pedig mennyi mindent tud...

,,Én úgy vélem, hogy a scriptek terén a legjobb megoldás az, amit a padtársam is ért, hogy ha hozzá kell nyúlnia, akkor ne kérdezzen bele, mert akkor kiderül, hogy már rég elfelejtettem, hogy azt miért pont úgy írtam. :-)''

Lehet, hogy képzettebb vagy tapasztaltabb padtársakat kellene választanod... ;-)

"Share what you know. Learn what you don't."

Értem, hogy vicc, de rossz. :-D

1) print $1"="$2"\n"

mivel a print a - szóközökkel(és nem vesszővel) elválasztott - paramétereit "összeragasztva" írja ki, ezért írható így, de ez már olvasható is:

print $1 "=" $2 "\n"

2) mivel a print - ha csak le nem tiltod, vagy át nem állítod - automatikusan küld egy ORS-t (alapérték "\n"), ezért minden kiírás után 2 soremelés lesz (azaz szöveg, üres sor, szöveg, üres sor, ... ). Ez nem nagyon látszik, hogy miért kell. Ha ehhez még azt is hozzáteszem, hogy ez az egész bekerül egy shell for ciklusának "lista" részébe, ahol viszont definíció szerint az elválasztó karakterek (szóköz, tabulátor, soremelés!!!!) darabszáma rohadtul nem számít (mert lenyeli őket a shell), én ezt betudom annak, hogy azt hitted, kell a print végén kérni soremelést. De nem, szóval szerintem felesleges.

3) egy cat | tr erősen szószátyár, helyette erőforrás-spórolósabb a "tr < indata.txt" forma. (Más parancsok viszont fájlnév paramétert is elfogadnak, tehát az átirányítás is valószínűleg megspórolható.)

4) Ha jól sejtem, MS-környezetből jön a szöveges fájl, ezért dobod ki a UNIX/Linux alatt felesleges kocsivissza jeleket. Szintén spórolásból érdemes elgondolkodni, hogy ha már úgy is meghívsz egy sed-et, akkor nem olcsóbb-e azzal megcsináltatni ezt is (a tr helyett), ehhez csak annyi kell, hogy a sed kapjon még egy -e 's/\r//g' opciót, és csak ez után jöjjön a -e 's/vcenter' rész.

5) a sort | grep olcsóbb, ha felcseréled őket - ekkor nem kell még a később eldobandó üres sorokat is rendezgetni - viszont ha sed | grep | sort van, akkor megint olcsóbb a grep -v helyett szintén egy plusz sed opcióval megcsináltatni ugyanezt a vcenteres opció után azaz

sed .... -s 's/vcenter...' -e '/^$/d' |

6) mindazonáltal, a grep-nek odaadott regexp közönséges (basic) regexp, felesleges a -E (extended) opció (mármint az lenne, ha maradna)

7) legvégül pedig minek rendezed a sed kimenetét, ha amúgy az x=y formájú elemekől asszociatív tömb x indexe és az ahhoz tartozó y érték lesz, és magával a tömbbel se csinálsz semmi olyasmit, ahol fontos lenne ez a bizonyos sorrend. Tudtommal dokumentáció szerint az asszociatív tömb indexeit *valamilyen* sorrendben kapod vissza, egyáltalán nincs definiálva, hogy abba a sorrendben, ahogy bevitted, de még ha úgy is kapnád, a felhasználásánál kb tök mindegy a sorrend. E miatt én a sort-ot ki is hagynám az egészből.

Szóval én kicsit kevesebb erőforrást használva így írnám:

 

for kv in $(awk '{ print $1 "=" $2 }' <(sed -e 's/\r//g' -e 's/vcenter..//g' -e '/^$/d' indata.txt ) ); do

csak egy megjegyzés:
 

UPDATE servers set backup='1716491210' WHERE hostname='dbs01';

ha jól értem akkor ez nem valami optimális update, van 18 ezer sorod, és marad 18 ezer a 700 helyett, 17 ezer redundáns sorral.

Ez egy nagyon egyszerű programozási feladat, nem adnék rá megoldást, mert biztos vagyok benne, hogy több jó megoldás is van a hozzászólások között, amiket nem olvastam teljesen végig. Bármilyen programozási nyelven ami nem indít soronként új processzt a párezer sor feldolgozása nevetségesen rövid idő lesz, kulcsonként ki kell gyűjteni a maximumot és azt betenni az adatbázisba a végén lehetőleg 1 vagy kevés tranzakcióban. De még az is lehet, hogy ha az eredeti megoldásnál maradsz, csak több update-et csokorba szedve csinálsz csak tranzakciót, már akkor is sokkal gyorsabb lesz.

Ami eszembe jutott róla, hogy volt idén a billion row challenge, ahol hasonló volt a feladat: CSV-szerű szövegfájlból kellett kulcsonként minimumot, maximumot és átlagot számolni (vagy ilyesmi). A győztes megoldás (lásd: https://github.com/gunnarmorling/1brc?tab=readme-ov-file#results ) a 1E9 sort (ez a billion angolul, ugye?) 01.535 másodperc alatt dolgozta fel. És ez a Java megoldás volt, több más programnyelven is készült pályamű, lehet hogy van ami még gyorsabb. Szóval ez legyen a benchmarkod a feladatodra!

Például 10 ezer sorral egyszerűen arányítva a számokat az jön ki, hogy 0,00001535másodperc alatt illene végezni. Érdemes belegondolni, hogy az eredeti megoldással ez a probléma annyira lassú volt, hogy egyáltalán szóba került, hogy optmimalizálni kell. Hányszoros optimálatlanság volt az eredeti megoldásban? Elgondolkodtató.

Az én, egyébként távolról sem optimális megoldásom már eleve úgy ezerszer gyorsabb volt mint az eredeti. És volt, van fontosabb dolog is, mintsem hogy kb. 4 másodpercről levigyem másfélre (ebben az update-k is benne vannak, erre még esetleg szánok időt, hogy egy tranzakció legyen a ~700 helyett).

Viszont, mivel biztos voltam benne hogy van jobb, így feldobtam ide, és sokat tanultam belőle.

Így már egész érthető. Van ilyen, hogy valamit megcsinálunk 1 db-ra, aztán egyszerűen csak ismételgetjük gondolkodás nélkül. Ugye SSH kapcsolatból is felépít párezret ahelyett, hogy csak egyet csinálna, így jön ki az elképesztő lassúság.

Szoktam mondani, hogy ha valami csak 1000x lassabb mint lehetne, akkor az már az esetek többségében jó megoldás. Van aki hüledezik erre, de ha mondjuk egy Arduino program egy pint a digitalWrite() paranccsal ír, akkor máris megvan 100x. Pedig az Arduino úgy él a többség fejében, hogy az valami alacsonyszintű programozás. Itt meg volt még egy kb ezerszeres szorzó még egyszer, ami együtt már egy milló, az már kicsit sok lett.

Szerkesztve: 2024. 05. 31., p – 09:38

De miért nem egyszerűbb egy subselectből beupdatelni a max(timestamp)-et?

Vagy ez már ósdi megközelítés?(: valamikor 20+ éve láttam sql-t)

Nyilván volt már bash-megoldás, de csak úgy hobbiból megpróbáltam:

#!/bin/bash

declare -A tab

while IFS=$'\n\r\t ' read A B C; do
    if [ x"$C" = x"" ]; then
        C="$B"
    fi
    if [[ -v tab["$C"] ]]; then
        if [ "$A" -gt "${tab[$C]}" ]; then
            tab["$C"]="$A"
        fi
    else
        tab["$C"]="$A"
    fi
done

printf '%s\n' "${!tab[@]}" | sort | while read i; do
    printf $'UPDATE servers SET backup=\'%s\' WHERE hostname=\'%s\';\n' "${tab[$i]}" "$i"
done

Ha a bash használók hozzászoknának, hogy a declare helyett a bash-ban natívan meglevő, a bash előtt egy évszázaddal a ksh-ban bevezetett typeset parancsot használnák, már eggyel hordozhatóbb lenne a kódjuk :-)

Az első if-ben nem kell az x után a "" :-)

De hogy mi a francnak rendezed te is ....

Nem azt írta, hanem azt, hogy egy bashism helyett lehet hordozhatóbb megoldást is használni. Egyébként én anno c-shellel kezdtem (Irix), aztán ksh jött (AIX, Solaris) - bash csak jóval később érkezett, de annak is soksok éve már... Ha csak egyszerű dolgok kellenek, akkor (szinte) észre sem veszed a különbséget, ha viszont scriptelni kell, akkor azért igen :-)
Volt olyan környezet, ahova adott verziójú pdksh-t kellett telepíteni, mert az xyz rendszer köré épített scripteknek arra volt szüksége... (Meg egy ócska ls binárisra, ami aztán okozott "érdekes" dolgokat...)

Ezek a "szarok" azért elég durva rendszerek tudtak lenni. Ami a portolást illeti: sok esetbena céges policy nem engedett mindenféle jött-ment szoftvert feltelepíteni - így bash nem mehetett rájuk (de az emlegetett ksh az van/volt rájuk natívan). És sokszor a hivatalosan megportolt és telepíthető szoftverek sok verzóval voltak lemaradva egy átlag Linux-terjesztésben levőhöz képest.

Hát pl. mert az az egyetlen, ami képes értelmezni a .sig -emet :-)

De amúgy zellernek igaza van, a hordozhatóságot próbáltam hangsúlyozni.

Hülyét tudok kapni attól, hogy a bash-fejlesztők - nagyjából totál értelmetlenül - olyasmiket is belefejlesztettek, ami kb. csak arra volt jó, hogy könnyen lehessen inkompatibilis kódot írni. Tehát nem arról beszélek, ami náluk lett először (vagy csak náluk van azóta is egyedül), hanem arról, amikor a Ksh-ban már évek (évtizedek) óta meglevő funkciókat implementáltak inkompatibilis módon. Ide tartozik ez a declare / typeset (kb. csak annyit kellett volna, hogy nem a declare formát hangsúlyozni mindenhol, hanem a typeset-et). Ilyen a függvények kezelése: kezdetben vala az x() forma, aztán a ksh-ban bevezették e mellé a function x formát, majd a bash fejlesztők kitalálták azt, hogy akár function x() formában is lehessen írni. (Eredménye, nagyon sok kódban szerepel ez az utóbbi forma.) De ilyen a . parancs C-shellesítése source néven. Vagy éppen a ksh-ban set -A array 1 2 3 formában bevezetett tömbváltozó kezelés helyett bevezetett array=( 1 2 3 ) forma. (De ha a typeset-nél és a .-nál van másik - kompatibilis - forma, akkor pont ezt miért nem vették át ugyanígy, hogy legyen jó mind a 2 szintaxis????) Vagy ahogy a Ksh-ban bevezetett aritmetikai helyettesítés - azaz a $(( 5 * 3 )) formát átvették, de azért mellécsapták (és promótálják, hogy ezt javasolt használni) helyette a $[ 5 * 3 ] formát. De hasonlóan, kb. 25 évvel a ksh után, bash-ban is megjelent a koprocessz kezelés lehetősége - és puff, egy teljesen a ksh-étól eltérő szintaxissal, még csak könnyedén átírni sem lehet. (ksh: p |& és utána read -p / echo -p szolgál a kommunikációra; bash-ban coproc kulcsszó, és egy, a koprocessz nevével megegyező nevű tömbváltozó 0-s és 1-s indexű elemén keresztül elérhető pipe-ok.)

A furcsa, hogy a bash doksikban egyértelműen benne van, hogy az un. POSIX-shell egy implementációjáról van szó. Azt pontosan lehet tudni, hogy a POSI-shell mint olyan pedig az akkor már évek óta létező (ksh88-as verziójú) Korn-shell alapján lett kitalálva (itt-ott módosításokkal) - no akkor ugyan miért nem lehetett a POSIX-shellben nem deklarált - de a ksh-ban létező funkciók újraimplementálásakor tartani a "őssel" a kompatibilitást.

Valószínűleg még lehetne találni pár ilyet - azaz olyat, ahol a "világuralomra törekvés" kivételével nem nagyon találni épelméjű magyarázatot a "miért így"-re.

 

(Ja, elvetemültebbek elkezdhetnek játszani a yash-sal - ami öndefiníciója szerint: a POSIX-compliant command line shell - persze nem 100%-ig kompatibilis se a ksh-val, se a bash-sal, se a zsh-val.)

-- 
#!/bin/ksh
Z='21N16I25C25E30, 40M30E33E25T15U!';IFS=' ABCDEFGHIJKLMNOPQRSTUVWXYZ ';
set -- $Z;for i;{ [[ $i = ? ]]&&print $i&&break;[[ $i = ??? ]]&&j=$i&&i=${i%?};
typeset -i40 i=8#$i;print -n ${i#???};[[ "$j" = ??? ]]&&print -n "${j#??} "&&j=;
typeset +i i;};IFS=' 0123456789 ';set -- $Z;
for i;{ [[ $i = , ]]&&i=2;[[ $i = ?? ]]||typeset -l i;j="$j $i";typeset +l i;};print "$j"

Értem, sőt nagyrészt egyet is értek vele, csupán a kérdésem nem erre irányult, hanem azon túl, h a értelmezni tudja a sigedet, miért jó ksh-t használni.

 

Azért kérdezem, mert ha van egy linux-only rendszer, akkor nem látom, h a portolhatóságnak lenne érdemben hozzáadott értéke.

 

Van ennek ráadásul egy másik oldala is. Teccik v. nem, a bash kb. monopol helyzetben van. Nem látom, mi előnye származhat bármelyik OS-nek, h ne lehessen alapból telepíteni. Azt szóba sem hozom, h miért nem az a default shell.

Hangsúlyozom, nem arról van szó, h jó vagy nem akár maga a shell, akár a monopol helyzet.

Még jelenleg is léteznek olyan UNIX-szerű operációs rendszerek -- pl. a BSD-k --, amelyeken az alaptelepítésben nincs bash shell.
Ezeknél a rendszerfolyamatokat valami más shell intézi (pl. sh, ksh, csh).
(De még talán Python vagy Perl sincs alapból...)

"Share what you know. Learn what you don't."

> Nem értem a BSD v. bármelyik másik problémáját, miért nem tud megoldani ennyit, ha erre van szüksége a userek többségének.

Nem, nincs. A felhasználók (már ha egyáltalán eljutnak parancssorig) kb annyit igényelnek, hogy a 4 kurzormozgató billentyű működjök a parancssor szerkesztése közben. Nem pedig arra, hogy declare -i formában hozzon létre a  fejlesztő egy csak egészek tárolására alkalmas változót.

Én szimplán sh, csh, ksh utat jártam be, és mivel a Korn-shell kb 99%-ban tudta és nagyjából a mai napig tudja azt amire nekem szükségem van, ezért sosem éreztem késztetést a bash-ra váltásra. Többé-kevésbé felismerem a bashizmeket, viszont mivel eddigi életemben jóval több időt töltöttem el a klasszik UNIX-világban mint Linuxok között, nekem az volt a természetes. Olyasvalakinek, aki Linux-only rendszerben dolgozik, értelemszerűen ez nincs. Viszont mivel még ma is simán bele lehet futni egyéb *X-rendszerbe, szerintem nem hátrány, ha egyszerűen tudatosítjuk azt, hogy "ván máásik". Ráadásul ha egy - hordozhatóság szempontjából - hibás beidegződésre felhívjuk a figyelmet, akkor később esetleg kisebb, vagy épp semennyi probléma nem lesz egy rendszerbeli változástól. (Nyilván a könyöködön jön ki, de attól még valós a hivatkozás arra, amikor a Debian valamiért úgy döntött, hogy dash lesz a default shell, és az első időben más se történt, mint a bashizmek kitakarítása a rendszerscriptekből.) És ahogy légrádi kolléga utalt rá: a BSD-k ugyan elérhetővé teszik a basht is, de a mai napig az sh / csh az amit alapból adnak. FreeBSD-ben az utóbbi években baromi sokat szöszöltek azzal, hogy az eredetileg tényleg inkább a Bourne-shell-re hajazó shell szépen kapja meg a POSIX-beli "extrákat". Ettől persze nem lesz Korn-shell, de erősen közelít. De pl. az OpenBSD ha jól tudom natívan ad egy pdksh-ból forkolt, asszem Mirksh nevű - hopp -, Korn-shell implementációt. És ha az ember megszokja használni ezeket a (Korn-shellből jövő) POSIX extrákat, akkor a végén kiderül, hogy nem is nagyon kell neki bash. Kivéve amikor egy szoftver fejlesztője (feleslegesen) teleszórja a szkriptjét bash-specifikumokkal.

Nekem ez az egesz ugy hangzik, mint ha valaki azert hisztizne, hogy miert hasznal valaki a Javas programjaban generikusokat, mert az csak a Java 8-ban (2014) jelent meg, o meg a mainframe-jere a Cobolos vacka melle csak 1.3-at tudott feltakolni, ugyhogy mindenki lesz szives 1.3-as feature set-et hasznalni. (Most a 22 korul jarnak.)

Ja, es kozben mar Win10-en sem jelent problemat egy bash script futtatasa. "hibás beidegződés".. hmm..

Egyebkent P>0 valoszinuseggel egy jobb LLM at tudja konvertalni a bash scriptet a honfoglalaskori megfelelojere, ha valakinek ez a becsipodese.

A strange game. The only winning move is not to play. How about a nice game of chess?

Azt érzem, hogy nem ment át amit mondani akarok,

Nem a a cél, hogy tessen ksh-scripteket (inkább: POSIX-shell nyelven)  írni. Nem azt mondom, hogy ne használjuk ki a bash specialitásait akkor, ha annak van értelme. Azt mondom, hogy ha van a bash-ban 2 (3 -4 - ...)  - egymással kompatibilis megoldás, akkor hosszú távon több értelme egy csepp odafigyeléssel azt a formáját használni, amit a világon sok más helyen meglevő eszközök is natívan megértenek, nem csak a bash. És a hangsúly azon van, hogy ha van kompatibilis forma, akkor azt használd (nem pedig azon, hogy ha van egy senki más által implementált egyedi dolog, azt ne használd, ha segít megoldani a feladatodat).

Pont erre volt példa a declare. Nyilván magamból indulok ki, de a typeset -i j és a declare -i j között nem látok érdemi különbséget Ránézésre mind a kettő homályos hogy mire is jó, de a második forma egy másik parancsértelmező (ksh) számára is ugyanúgy érthető, nem csak a bash számára - én ezért ezt preferálom, és ennek használatára buzdítok mindenkit. Ha pedig felhasználói szemmel nézem, akkor meg mind a kettőt kenterbe veri az integer j, mert ránézésre látszik, hogy mit takar. Mondjuk ennek meg az a nagy hátránya, hogy csak a ksh érti, de a bash (meg a yash) nem.

Egy lépéssel továbbmenve:

Fent emlegetett .sig -emben nem azért van typeset -i, mert ez hordozható, hanem mert be akartam mutatni a Korn-shell egyedi funkcióját - ott ugyanis lehet alapértelmezett számrendszert is beállítani a (z integer tipusú) változókra, és ezt kihasználva játszottam a számrendszerek közti konverzióval - 8-as és 40-es alapú számrendszereket használva.

Konklúzió: ha az a célom, hogy egy kódot *erőszakosan* hozzákössek egy környezethez, akkor ha integer j -t írok, akkor ettől ksh-hoz lesz kötve, ha meg declare -i j -t, akkor a bash-hoz. Ha pedig az a cél, hogy kevesebb legyen a portolási szopás, akkor azt, hogy typeset -i j.

Kérdés: egy Linux-only környezetben mit izgasson a portolhatóság? Mivel keveseknek van varázsgömbjük, ezért a következő - múltban egyszer vagy többször már megtörtént események alapján - nem kizárható dolgok miatt *nekem* ésszerűnek tűnik a portolást nem igénylő forma:

- olyan rendszer szintű váltás, mint volt a Debian bash-dash. Ha nincs bashizm, csak "POSIXizm", akkor 0 költsége van a váltásnak.

- a Linux-only környezetbe bekerül egy-két cél-BSD (BSD-alapú tűzfal, BSD-re épített NAS pl.), vagy épp egy dinoszaUNIX. Lehet, hogy telepíthető ugyan a bash, de nem biztos hogy célszerű - mondjuk egy tűzfal esetén inkább minimalizáljuk a fent levő szoftvereket, mint bővítsük a listát (csak a kényelem / megszokás lrdekében).

- megváltozó gondolkodás. Sok ember váltott már OS-t, Win - Linux - Win - Linux. Nem nagyon sokan,. de Linux - BSD váltás is történt, és milyen egyszerű is, az évek alatt - odafigyeléssel előállított vackaink vihetők, és semmit nem kell módosítani.

(Funfact: a shellshock nem működött ksh alatt, mert azt a baromságot amit a bash-fejlesztők kiagyaltak az exportált függvények *megvalósíthatósága* érdekében, azt ksh-ban egyszerűen nem implementálták. Holott akit érdekel, megfigyelheti, hogy a ksh93-ban rendszeresen jelennek meg olyan funkciók, amik nincsenek POSIX-ban, de bash-ban igen - és hasznosnak is tűnik.)

Ezek is jó dolgok, de azért érdemes megnézni a script legelső sorát is: oda van írva az elvárt interpreter neve.

Mondjuk beleírhatom én a scriptbe, hogy `export LC_CTYPE=hu_HU.ISO-8859-2` egyes egzotikus (nem-bash) shellek simán rámondhatják, hogy "ja, ez rám nem vonatkozik, én a saját indulásomkor érvényes LC_CTYPE-pal dolgozom tovább". (Saját mérési adatom Aix+ksh)

Speciel a POSIX azt mondja, hogy #!/bin/sh legyen az első sor. Továbbá olyan is előfordul, hogy a script valamiért noexec-es FS-en van, ekkor a ./izémizé tipusú futás - ahol a sheebang számítana - nem működik, ilyenkor az emberek jelentős többsége simán sh ./izémizé formában futtatja. (Nyilván van olyan is, aki bash izémizé - ként) Máris megvan a baj, mert ilyenkor már a sheebang nem számít.

Ha egy interpreter felülbírálja a benne előírt utasításokat, akkor ott már mindenféle egyéb baj is lehet, azt a programot ki kell dobni. :-) Kérdés: ha már AIX, nem lehet, hogy az ott nem ksh, hanem az ő csodás POSIX-shelljük volt (a'la bsh)? Én egyébként az ilyenek miatt 2 dolgot szoktam csinálni (már az anyázáson kívül, mert az kevéssé hatékony) :

- a nyelvi beállításoknál *mindig* a hagyományos szintaxist használom, példádban:

LC_CTYPE=hu_HU.ISO-8859-2

export LC_CTYPE

 

- és ha véletlenül neki az induláskori érvényes beállítás számít, akkor pedig így futtatom:

$ env LC_CTYPE=hu_HU.ISO-8859-2 ksh izémizé

és igen, sh-szintaxis esetén az "env" kihagyható, de ebben a formában viszont minden értelmes shell megeszi, lehet az Bourne, C, Korn, stb. No így már mindenhol azt látja, hogy magyart szeretnék.

"Kérdés: ha már AIX, nem lehet, hogy az ott nem ksh, hanem az ő csodás POSIX-shelljük volt (a'la bsh)?"

És lassan eljutunk oda, hogy kinek a "ksh"-ja kompatibilisebb az eredetivel, az IBM posix shell, vagy a pdksh, vagy épp a Microsoft-os verzió... :-D

Pontosan. Hiszen amikor valaki újraimplementál egy már létező szoftvert, akkor ritkán szokott sikerülni pontosan ugyanolyanra. (*) Korai unixos életem eléggé meghatározó élménye volt, amikor egy addigra "befejezett" állapotúnak minősített scriptem eljutott abba a fázisba, hogy akkor most mehetnek bele a külvilágnak szóló megjegyzések, hogy egy hét múlva is érthető legyen, És meglepődve konstatáltam, hogy az addig működő program a kommentek elhelyezése után nem hogy nem jól működött, de eleve el se indult valami "Illegal EOF"-jellegű üzenettel. Eltartott egy darabig, mire kiderült, hogy az adott shell egy érdekes hibával rendelkezett: azt ugyan tudta, hogy a # -től a sor végéig levő dolgok nem számítanak, de úgy volt vele, hogy ennél sokkal fontosabb, hogy az aposztrófoknak "párban" kell állniuk, és abba döglött bele, hogy betettem valami ilyen megjegyzést egy sor végére:

blablabla # it's working .....

 

Viszont nem meglepő módon nem "zártam be" a "nyitó" aposztrófot. Valószínűleg ez se minden shell-implementációban volt / van létező hiba.

 

(*) nvi: "a bug-to-bug compatible reimplementation to the original Berkeley-vi"

Röviden és egyszerűen:

awk '{if ((! $1 in a)||($1>a[$NF])){a[$NF]=$1}}END{for(k in a){print "UPDATE servers set backup=\047"a[k]"\047 WHERE hostname=\047"k"\047;"}}' lista.txt

De ruby-ban lényegesen kényelmesebb:

#!/usr/bin/env ruby
h=Hash.new
File.open(ARGV[0]).each do |line|
  time,host=line.chop.split(/ .* /)
  h[host]=time unless h.has_key?(host) and h[host]>time
end
h.each do |host,time|
  puts "UPDATE servers set backup='#{time}' WHERE hostname='#{host}';"
end

És awk-kal is lehet szebben. Lehet pipe végére tenni, vagy paraméterként is megeszi a fájlnevet.
És ebben a formában még az aposztrofokkal sem kell bajlódni:
 

#!/usr/bin/env -S awk -f
{
  if ( (! $1 in a) || ($1>a[$NF]) ) {
    a[$NF]=$1
  }
}
END{
  for (k in a) {
    print "UPDATE servers set backup='"a[k]"' WHERE hostname='"k"';"
  }
}

Sok lett, de miért nem olvasol is közben :-) ? Az awk kódod szerda óta ott van (igaz egyszerűbben ;-) )

Amúgy ez a perl kód mit csinál akkor, amikor 3 tagból áll a sor? Én  ugyanis úgy tudom, hogy a regexp illeszkedésnél az a szabály, hogy a sorban előrébb levő szövegre fogja illeszteni a mintát, azaz a timestamp virtualmachinehost virtualmachinename hármasból TS és VMhost illeszkedés lesz e miatt.

Az első n válasz közt nem láttam megoldást, viszont láttam hagy van még millió hozzászólás, ebből hibásan arra következtettem, hogy még mindig nincs válasz. Gondoltam jó fej leszek :)

Az awk kódban tényleg el lehet hagyni a feltétel első felét. Furcsa, hogy először én is úgy írtam meg, de valami miatt nem ment, utána bővítettem. Már kinyomozhatatlan, mit szúrhattam el elsőre.

Perl: mindig a leghosszabb illeszkedést keresi a regexp, így itt az egész sorra illeszkedik, vagyis a $2 értéke VMname lesz mindenképp.
 

> Perl: mindig a leghosszabb illeszkedést keresi a regexp, így itt az egész sorra illeszkedik, vagyis a $2 értéke VMname lesz mindenképp.

Khmmm.

$ echo alma korte szilva | perl -ne 'print "$&\n" if ( /[a-z]*/ ); '
alma

 

Szóval altalában regexp-ek kapcsán:

1) a legelső (pozíció szerint) találat számít

2) ha azonos pozícióban van több különböző hosszúságú találat, akkor a leghosszabb illeszkedő találat "nyer"

 

Ha fenti 1) nem lenne, akkor a szilva kéne kiírva legyen.

No rájöttem, hogy mi az amit nem sikerült elég érthetően megfogalmaznom.

> if ( /([^ ]+).* (.+)/ 

Amikor

TS  vhost vguest

formájú a sor, akkor szerintem két lehetséges kimenete lesz fenti regexp-nek:

1) az elvárt működés, hogy a nem megjegyzett középső tag - azaz a ".* " begyűjti az első szóköztől az utolsóig plusz vhost sztringet, és a végén álló megjegyzett tagnak - azaz a "(.+)" - marad $2-be gyűjtendőnek a vguest. Ekkor jók vagyunk.

2) nem elvárt működés, de szerintem semmi nem zárja ki, hogy a ".*" (szóköz nélkül) nulla hosszúságú illeszkedést produkál (ilyen van), azaz megmarad a " vhost vguest", amit begyűjt a " (.+)" minta (amiben ott a szóköz az elején), azaz $2 a bevezető szóköz nélkül megkapja a "vhost vguest" sztringet,ami nem annyira jó eredmény.

Azt nem tudom, hogy ez valahol explicit le van-e írva, hogy a perlben ez sosem fordulhat elő, vagy csak a gyakorlati tapasztalatokból indulunk ki. Mert ha utóbbi, azaz sima UB (undefined behaviour - nem definiált viselkedés), akkor én nem építenék rá. Helyette szimplán csak annyit kell tenni, hogy ahogy az első megjegyzett mintában kizárjuk a szóközt, ugyanúgy a regexp végén is:

> if ( /([^ ]+).* ([^ ]+)/