Argumenty przekazywane do procesu (również bash)

Powłoka czyta polecenia jako jeden ciąg tekstowy. Następnie przeprowadza analizę wczytanych danych. O sposobie analizy decyduje:
a) Zmienna IFS, powodująca, że domyślnie argumenty oddzielone są spacjami.
b) Jednocześnie powłoka umożliwia obejmowanie części poleceń pojedynczymi lub podwójnymi cudzysłowami. Cudzysłowy powodują wyłączenie specjalnego interpretowania niektórych znaków, jak choćby spacje.
c) Interpretowane są znaki wieloznaczne (wildcards), jeżeli ich interpretacja jest włączona (wtedy następuje ich podmiana).


Tablica argc[]:
a) argc[0] - nazwa polecenia
b) argv[argc-1] - ciąg odpowiadający ostatniemu argumentowi
c) argv[argc] - null odpowiadający końcowi tablicy. Czyli przekazując wskaźnik argv+1 faktycznie przekazujemy listy argumentów bez nazwy polecenia.


Pełna deklaracja funkcji main():
int main(int argc, char const *argv[], char const *envp[]) {
Uproszczona deklaracja funkcji main()
int main(int argc,char *argv[], char *envp[]) {

int main(
    int argc,      /* licznik argumentów
    char *argv[]   /* tablica argumentów
)


Wniosek:
Każdy napis argumentu może zawierać cokolwiek, byle był zakończany null. Jest problem z przekazywaniem binarnej wartości "zero".


Warto zobaczyć:
https://github.com/torvalds/linux/blob/master/lib/argv_split.c
Oraz:
- getopt()
- argp_parse()


Tablica envp[]:
- ostatni element to null
- nie ma licznika (takiego jak w argv[] stanowi argc)
- Dostęp do środowiska uzyskiwany przez
extern char **environ;
Jest równoznaczny z environ[x] odpowiadającemu envp[x]


Aby przetworzyć listę dowolnego rodzaju:
mydata=("$somedata"*.txt)
program "${mydata[@]}"


Nazwami plików zaczynające się od -? (kreska lub minus):
Większość poleceń zinterpretuje jako oznaczenie opcji. Można użyć oznaczeni końca opcji:
set -- "$filelist"*.txt
program -- "$@"
Możesz też upewnić się, że nazwy plików zaczynają się od znaku innego niż - (kreska lub minus) Można dodać "/" lub "./"
case "$f" in -*) "f=./$f";; esac


Pozostaje jeszcze interpretacja jako standardowego wejścia lub standardowe wyjście.  Sprawdź różnicę:
"du -sh *"
a,
"du -sh ./*"?


Przypisanie polecenia do zmiennej - przykłady:
command_path="$1"
"$command_path" --option --message="test"

cmd=(/bin/program --option --message="test" --)
cmd=("${cmd[@]}" "$file1" "$file2")
"${cmd[@]}"

set -- /bin/program --option --message="test" --
set -- "$@" "$file1" "$file2"
"$@"

code='/bin/program --option --message="test" -- /var/local/file1 | grep "test"'
eval "$code"

Jeszcze lepszym rozwiązaniem jest użycie funkcji.


Polecenia xargs / find - podpowiedzi:
xargs -L1
xargs -l
xargs -0
find cos -exec program parm1 parm2 parm3 {} +

find cos -exec sh -c '
  for x do
    program "$x"
  done
' find-sh {} +


Maksymalna długość (wielość) przekazywanych argumentów:
1) Silnie zależny od systemu.
2) Wypróbuj:
- getconf ARG_MAX (od wersji 4.4BSD i System V)
- sysconf(_SC_ARG_MAX)
- sys/limits.h  ARG_MAX
- xargs --show-limits
3) Test:
MAXA=`perl -e 'print "a"x150000'`
bash test.sh $MAXA

Patrząc na ARG_MAX/NCARGS, musisz wziąć pod uwagę konsumpcję przestrzeni zarówno przez argv[] i envp[] (argumenty i otoczenie).
Niektóre powłoki umożliwiają eksportowanie funkcji do środowiska. Powyższa sytuacja lekko się komplikuje, ponieważ ich definicje zawierają nowe znaki, które zmogą zostać błędnie zinterpretowane jako nowe envp[].  To samo dotyczy sytuacji, gdy wartości zmiennych zawierają znaki nowej linii.

Od 2.6.23 jest inaczej, niż w UNIX'ach:
- Dodano: AX_ARG_STRLEN i MAX_ARG_STRINGS.
- ARG_MAX zazwyczaj będzie o wielkości 1/4 stosu (RLIMIT_STACK).
- Dla systemów wbudowanych bez MMU limit nie ma zastosowania.

Dokładnie jest to sprawdzane w:
- "do_execve()" w fs/exec.c  ( PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *) / sizeof(void *) )
- linux/binfmts.h


Można próbować używać poleceń typu:
expr `getconf ARG_MAX` - `env|wc -c` - `env|egrep '^[^ ]+='|wc -l` \* 4 - 2048
W teorii powinny dać jakieś poglądowe wartości, nawet uwzględniając istnienie funkcji powłoki w tym obszarze pamięci, ale faktycznie zazwyczaj zawyżają wartości. Używając jednak:
getconf ARG_MAX
I wiedząc, że:
- Długość tablicy wskaźników do tych ciągów to zwykle 8 bajtów dla x86_64
- Używając również: limit stacksize lub ulimit -a
Otrzymujemy: jeden milion pustych argumentów utworzy listę wskaźników o wielkości 8 MB, a taki jest rozmiar stosu na moim 64 bitowym Debianie.
Lecz trzeba pamiętać, że w Linuksie maksymalny rozmiar pojedynczego argumentu to 128 kiB.
1000 = kB kilobyte
1024 = KiB kibibyte

Jak uniknąć ograniczeń przekazywanych argumentów?
for i in *; do command "$i"; done  #powolne
printf '%s\0' *|xargs -0 command
find . -exec command {} \;     #powolne
find . -exec command {} +
find . -print0|xargs -0 command
find . -print|xargs command   #bez spacji
"find . ! -name . -prune [...]"
cd /directory/with/long/path; command *
command [a-e]*; command [f-m]*; ...
Nie używać argumentów do przekazywania dużych ilości danych.


********

Więcej informacji:
Informatyka, FreeBSD, Debian


***

Inne wpisy:



Update: 2018.06.12
Create: 2018.06.12