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 int
y a char
y 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 int
e 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 int
och.
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 #include
núť!
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 #include
núť.
#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;
}