Z Javy migrujuvší céčkar 6: putsy, argvá, a znaky dnu/von

S programom, čo nekomunikuje s vonkajším svetom, nie je sranda. Kto nič nepotrebuje a nič neposkytuje, nemusí existovať.

Našťastie, céčkarské programy je skoro nemožné napísať tak, aby nerobili vôbec nič.

Lebo aj najhlúpejší program niečo robí:

int main(void) {
    return 0;
}

Vracia do operačného systému cez návratovú hodnotu main() návratový kód (exit kód) nula, ktorý možno v rozumnom shelli veľmi ľahko zistiť: v Bashi cez premennú $?.

gcc nic.c -o nic
./nic
echo $?

A áno, figliari môžu využiť prasyntax C a napísať najmenší spustiteľný program:

main() {}

lenže i tak napíšu len metódu, ktorá vracia int s náhodnou hodnotou.

Ale to sme odbočili.

Tak či onak, návratový kód je najjednoduchší spôsob, akým možno vracať hodnotu a linuxovské programy to hojne využívajú, ak chcú oznámiť, či zbehli v poriadku a ak nie, aký bol dôvod.

O stupienok lepšia možnosť je…

puts()

Klasický protipól javáckeho System.out.println() odsúdený na radostné vypisovanie reťazcov na štandardný výstup:

#include<stdio.h>

int main(void) {
    puts("Ahoj svet!");
    return 0;
}

Áno, je už trochu neskoro na Ahoj svet, ale čo lepšie vymyslieť? puts() vypíše jeden reťazec na štandardný výstup… a úplne zadarmo k nemu dodá aj znak konca riadka \n! Ale viac od toho nečakajte: na všetko ostatné slúži inteligentnejší súrodenec: printf(), ale o ňom neskôr.

Pretože sme stále nič nepovedali o vstupe údajov do programu.

Parametre z príkazového riadka: Argcá a argvá

Pchať údaje do programu možno mnohými spôsobmi, ale opäť existuje jeden najprimitívnejší: cez parametre príkazového riadka. Hľa, fiktívny program jest spustený:

./args Milujem C

Tak ako v Jave vyzerá metóda main() nasledovne:

public static void main(String[] args)

V poli args sa objavia parametre z príkazového riadka, pričom na nultej pozícii je názov programu. V ukážke bude mať teda pole tri prvky a na nultom indexe sa ocitne reťazec ".args".

Java to ukradla z C, v ktorom však musíme uviesť nielen pole (argv, argument vector, teda vektor argumentov), ale aj jeho dĺžku (argc, argument count, počet argumentov), lebo nemáme žiaden argv.length.

Cé verzia je:

int main(int argc, char **argv)

Ak vidíte hviezdičky pred očami, stačí zatiaľ vedieť, že argv je premenná typu pole reťazcov.

Chcete vysvetlenie? OK:

Druhý parameter čítame ako „pointer na pointer na char. Pointery / smerníky / ukazovatele síce zrejme nepoznáme, ale definíciu si môžeme prepísať na:

int main(int argc, char *argv[])

To znamená „pole pointerov na char“, a keď vezmeme do úvahy fakt, že pointer na char je mnohokrát „niečo ako reťazec“, a poskladáme si to dohromady, zistíme, že je to naozaj pole reťazcov.

Koniec vysvetlenia, dajme si príklad!

Ak chceme vypísať všetky parametre programu, okrem jeho názvu, je to ľahké:

#include <stdio.h>

int main(int argc, char **argv) {
        int i;

        for(i = 1; i < argc; i++) {
                puts(argv[i]);
        }
        return 0;
}

Najväčšia zrada je cyklus, ktorý začína jednotkou, čím preskočíme nultú položku s názvom programu. Ak to teraz spustíme:

./args Milujem C

Uvidíme:

Milujem
C

Znaky dnu a von: súrodenci getchar() a putchar()

Skúsme teraz toto: vytvorme program, ktorý automaticky vygeneruje náhodné desaťznakové heslo. A aby to bolo trochu náročnejšie: nech obsahuje veľké a malé písmená, alebo čísla.

Najprv Java kód!

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;

public class AlnumFilter {
    private static final int maxLength = 10;

    public static void main(String[] args) throws IOException {     
        Reader stdin = new InputStreamReader(System.in);
        int readInt;
        int count = 0;
        while((readInt = stdin.read()) != -1 && count < maxLength) {
            char readChar = (char) readInt;
            if(Character.isLetterOrDigit(readChar)) {
                System.out.print(readChar);             
            }
            count++;
        }
    }
}

Na Linuxe môžeme skúsiť napríklad:

java -cp . AlnumFilter < /dev/urandom

Prekvapivo, kód v C bude jednoduchší, aj keď s jemným WTF.

Na načítanie znaku zo štandardného vstupu použime funkciu getchar(), ale … tá vracia int! Za ním sa skrýva buď priamo načítaný znak alebo hodnota schovaná za konštantou EOF (end-of-file), ak nastane koniec súboru.

Prečo tá podivnosť? Presne kvôli stavu “buď normálny znak, alebo EOF“. A pozor, kým v Jave vracajú read()y vždy mínus jednotky, tu to tak môže, ale nemusí byť. Preto tá konštanta.

Tieto inty a chary sa voľne zamieňajú tiež preto, že sa v C spoliehame na bežnú ASCII znakovú sadu: 8-bitov = 255 rozličných znakov a teda také A sa v inte načíta ako 65 (jeho ASCII hodnota) a teda znaky a čísla možno do istej miery voľne zamieňať.

Ale naozaj to nie je také komplikované, ako to vyzerá.

Budeme ešte potrebovať funkciu na zápis znaku do štandardného výstupu: skamarátenou funkciou je putchar()… a tá berie opäť int.

Špeciálne pre túto úlohu využijeme ešte funkciu, ktorá zistí, či znak (int!) je alfanumerický: využijeme isalnum(), ktorá rovnako fičí na intoch.

Prvá verzia bude vyzerať ako filter na veľké/malé písmená a cifry a nebude veľmi Cčkoidná:

#include<stdio.h>
#include<ctype.h>

int main(void) {
        int c;
        while(1) {
                c = getchar();
                if(c == EOF) {
                        break;
                }
                if(isalnum(c)) {
                        putchar(c);
                }
        }

        return 0;
}

V zdanlivo nekonečnom cykle budeme načítavať znaky. Ak naďabíme na koniec súboru, končíme, a v opačnom prípade zistíme, či je znak správneho typu a ak áno, vypíšeme ho.

Len dva technické čriepky: getchar() a putchar() sú z knižnice stdio; funkcia pre alfanumerickosť zase z ctype, ktoré nezabudneme #includenúť!

Skúsme si to spustiť na Linuxe! Špeciálny nekonečný súbor /dev/urandom slúži ako systémový generátor náhodných znakov. Stačí z neho načítavať do nemoty a máme prúd pseudonáhodných dát!

cat /dev/urandom

… a máme matrixovské pozadie (mínus zelené farby).

Ak to prepojíme s našim programom:

./password < /dev/urandom

…máme nekonečný prúd veľkomalých písmen prekladaný ciframi.

V záchvate radosti z funkčnosti môžeme vypeknieť program::

#include<stdio.h>
#include<ctype.h>

int main(void) {
        int c;
        while( (c = getchar()) != EOF ) {
                if(isalnum(c)) {
                        putchar(c);
                }
        }

        return 0;
}

Ale obohaťme ho o načítanie obmedzeného počtu znakov:

#include<stdio.h>
#include<ctype.h>

#define DEFAULT_LENGTH 10

int main(void) {
        int current_length = 0;
        int c;

        while( (c = getchar()) != EOF && current_length < DEFAULT_LENGTH ) {
                if(isalnum(c)) {
                        putchar(c);
                        current_length++;
                }
        }

        return 0;
}

Ak to chceme úplne vylepšiť a skombinovať to s parametrami z príkazového riadka, budeme potrebovať bonusovú premennú pre dĺžku: tá bude mať preddefinovanú hodnotu z konštanty (10) alebo ju získa z príkazového riadka.

Akurát pozor na dátové typy: z príkazového riadka príde reťazec, a my budeme potrebovať int. Rýchly, aj keď značne hlúpy a deravý prevod zabezpečí funkcia atoi() z knižnice stdlib, ktorú musíme #includenúť.

#include<stdio.h>
#include<ctype.h>
#include<stdlib.h>

#define DEFAULT_LENGTH 10

int main(int argc, char **argv) {
        int current_length = 0;
        int default_length = DEFAULT_LENGTH;
        int c;

        if(argc == 2) {
                default_length = atoi(argv[1]);
        }

        while( (c = getchar()) != EOF && current_length < default_length ) {
                if(isalnum(c)) {
                        putchar(c);
                        current_length++;
                }
        }

        return 0;
}

Použitie potom bude napríklad:

./password 12 < /dev/urandom

Bonusovka

Ak máte bežný shell, úlohu vyriešite zoradením tr a head. Vysvetlenie je v článku o generovaní hesiel pre loginy v shelli.

tr -dc [:alnum:] < /dev/urandom | head -c ${1:-$PASSWD_LEN}

Bonusovka 2

Program sa dá ešte zoptimalizovať: namiesto konštanty a premennej pre maximálny počet a neustále zvyšovanej premennej pre aktuálnu dĺžku hesla môžeme použiť len premennú s ostávajúcimi znakmi, ktoré treba vygenerovať. Tú budeme postupne znižovať, aha:

#include<stdio.h>
#include<ctype.h>
#include<stdlib.h>

#define DEFAULT_LENGTH 10

int main(int argc, char **argv) {
        int remaining = DEFAULT_LENGTH;
        int c;

        if(argc == 2) {
                remaining = atoi(argv[1]);
        }

        while( (c = getchar()) != EOF && remaining > 0 ) {
                if(isalnum(c)) {
                        putchar(c);
                        remaining--;
                }
        }

        return 0;
}

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *