Pisanie znaku N razy za pomocą polecenia printf

12

Znalazłem następującą komendę, aby powtórzyć znak w systemie Linux:

printf 'H%.0s' {1..5000} > H.txt

Chcę na przykład Hpowtarzać 5000czasy. Co to %.0sznaczy tutaj?

gwiazda
źródło
Z tcshlub zsh, repeat 5000 printf Hjest łatwiejsza do zrozumienia. Z perl: print "H" x 5000(zauważ, że {1..5000}to zsh operator zainspirowany perl„s 1..5000jednym i później kopiowane przez ksh93 i bash)
Stéphane Chazelas
tak, to działa, ale zużywa dużo zasobów na większe powtórzenia, postępuj zgodnie z sugestiami Stéphane Chazelas
Skaperen
1
Zrobiłbym to polecenieyes H|head -5000|tr -d '\012'
Skaperen
dd if=/dev/zero bs=5000 count=1 | tr '\0' H
kojiro,
@Skaepren:yes H| head -n 2500| tr \\n H
mikeserv

Odpowiedzi:

20

To polecenie zależy od powłoki, która generuje 5000 argumentów i przekazuje je, printfa następnie je ignoruje. Chociaż może się to wydawać dość szybkie - i jest związane z niektórymi rzeczami - powłoka musi nadal generować wszystkie te ciągi jako argumenty (i rozgraniczać je) i tak dalej.

Oprócz faktu, że wygenerowane Hs nie może zostać wydrukowane, dopóki powłoka nie wykona iteracji po raz pierwszy do 5000, to polecenie kosztuje również w pamięci wszystko, czego potrzeba, aby przechowywać i ograniczać argumenty ciągów liczbowych do printf plus Hs. Tak samo po prostu możesz zrobić:

printf %05000s|tr \  H

... który generuje ciąg 5000 spacji - które przynajmniej są zwykle tylko jednym bajtem na i nic nie kosztują, ponieważ nie są rozdzielane. Kilka testów wskazuje, że nawet za 5000 bajtów koszt widelca i wymaganej rury trjest tego wart nawet w tym przypadku i prawie zawsze ma miejsce, gdy liczba rośnie.

Pobiegłem ...

time bash -c 'printf H%.0s {1..5000}' >/dev/null

...i...

time bash -c 'printf %05000s|tr \  H' >/dev/null

Każde około 5 razy w kawałku (tutaj nic naukowego - tylko anegdotyczne) i wersja rozszerzenia nawiasów średnio trtrwało nieco ponad 0,02 sekundy w łącznym czasie przetwarzania, ale wersja pojawiła się w przybliżeniu w sumie około 0,012 sekundy - i trwersja pobiła go każdego razu. Nie mogę powiedzieć, że jestem zaskoczony - {brace expansion}jest to przydatna interaktywna funkcja skrótu powłoki, ale zwykle jest raczej marnotrawstwem w przypadku wszelkiego rodzaju skryptów. Wspólna forma:

for i in {[num]..[num]}; do ...

... kiedy się nad tym zastanowić, to tak naprawdę dwie for pętle - pierwsza jest wewnętrzna i implikuje to, że powłoka musi się w jakiś sposób zapętlić, aby wygenerować te iteratory przed zapisaniem ich wszystkich i powtórzeniem ich dla forpętli. Takie rzeczy są zwykle lepiej wykonywane, jak:

iterator=$start
until [ "$((iterator+=interval))" -gt "$end" ]; do ...

... ponieważ przechowujesz tylko kilka wartości i nadpisujesz je podczas pracy, a także wykonujesz iterację podczas generowania iteracji.

W każdym razie, podobnie jak wspomniane wcześniej wypełnienie spacji, możesz również printfzerować dowolną liczbę cyfr, na przykład:

printf %05000d

Robię oba bez argumentów, ponieważ dla każdego argumentu określonego w printfłańcuchu formatu, gdy argument nie zostanie znaleziony, używany jest łańcuch zerowy - co jest interpretowane jako zero dla argumentu cyfrowego lub pusty ciąg dla łańcucha.

Jest to druga (i - moim zdaniem - bardziej wydajna) strona medalu w porównaniu z poleceniem w pytaniu - podczas gdy nie można nic z niczego uzyskać, tak jak robisz, gdy printf %.0długości ciągów dla każdego argumentu, więc również jest można uzyskać coś z niczego.

Jest jeszcze szybszy dla dużych ilości generowanych bajtów, których możesz użyć, ddtakich jak:

printf \\0| dd bs=64k conv=sync 

... i / regularny w pliki dd„s seek=[num]argument może być używany do większej przewagi. Możesz uzyskać 64 tys. Nowych linii zamiast zer, jeśli dodasz ,unblock cbs=1do powyższego, a stamtąd możesz wstrzykiwać dowolne ciągi na linię za pomocą pastei /dev/null- ale w takim przypadku, jeśli jest on dostępny, możesz również użyć:

yes 'output string forever'

Oto kilka innych ddprzykładów:

dd bs=5000 seek=1 if=/dev/null of=./H.txt

... który tworzy (lub skraca) do \0NULpliku wypełniony bieżącym katalogu o nazwie H.txt o rozmiarze 5000 bajtów. dddąży prosto do przesunięcia i NUL-wypełnia wszystko za nim.

<&1 dd bs=5000 conv=sync,noerror count=1 | tr \\0 H >./H.txt

... który tworzy plik o tej samej nazwie i rozmiarze, ale wypełniony znakami w / H. To wykorzystuje dd„s spec''d zachowań pisać co najmniej jeden pełny blok zerowy w przypadku błędu odczytu, kiedy noerrori synckonwersje są określone (i - bez count=- będzie prawdopodobnie trwać dłużej niż można chcieć) i celowo przekierowuje deskryptor pliku tylko do zapisu na ddstdin.

mikeserv
źródło
8

Sposób %.0sprzekonwertowania argumentu na ciąg znaków z dokładnością do zera. Zgodnie z man 3 printf, wartość precyzji w takim przypadku daje

   [ ... ] the  maximum  number  of characters to be printed from a
   string for s and S conversions.

dlatego gdy precyzja wynosi zero, argument łańcuchowy nie jest w ogóle drukowany. Jednak H(który jest częścią specyfikatora formatu) jest drukowany tyle razy, ile jest argumentów, ponieważ zgodnie z printfsekcjąman bash

The format is reused as necessary to consume all  of  the  argu
ments.  If the format requires more arguments than are supplied,
the extra format specifications behave as if  a  zero  value  or
null  string,  as  appropriate,  had  been supplied. 
steeldriver
źródło
7

W takim przypadku %.0szawsze drukuje jedno wystąpienie poprzedzającego go znaku (znaków), w tym przypadku H. Gdy użyjesz {1..5000}, powłoka rozwija go i staje się:

printf 'H%.0s' 1 2 3 4 ... 5000 > H.txt

tzn. polecenie printf ma teraz 5000 argumentów, a dla każdego argumentu otrzymasz jeden H. Nie muszą one być sekwencyjne ani numeryczne:

printf 'H%.0s' a bc fg 12 34

wypisuje HHHHH- tzn. liczbę argumentów, w tym przypadku 5.

Uwaga: elipsy w pierwszym przykładzie powyżej nie są wstawiane dosłownie, lecz służą do wskazania sekwencji lub zakresu.

KM.
źródło