Czy jest lepszy sposób na uruchomienie polecenia N razy w bash?


304

Od czasu do czasu uruchamiam wiersz poleceń bash w następujący sposób:

n=0; while [[ $n -lt 10 ]]; do some_command; n=$((n+1)); done

Aby uruchomić some_commandkilka razy z rzędu - w tym przypadku 10 razy.

Często some_commandjest to łańcuch poleceń lub potok.

Czy jest na to bardziej zwięzły sposób?


1
Oprócz innych fajnych odpowiedzi możesz użyć let ++nzamiast n=$((n+1))(3 mniej znaków).
musiphil

1
Przydatne byłyby pewne wskazania, które z tych metod są przenośne.
Faheem Mitha,


3
Jeśli chcesz zmienić muszle, zshma repeat 10 do some_command; done.
chepner

1
@JohnVandivier Właśnie wypróbowałem go bash w świeżym kontenerze dokowanym 18.04, oryginalna składnia podana w moim pytaniu nadal działa. Może używasz powłoki innej niż bash, takiej jak / bin / sh, co jest naprawdę myślnikiem, w którym to przypadku wracam sh: 1: [[: not found.
bstpierre

Odpowiedzi:


479
for run in {1..10}
do
  command
done

Lub jako jedna linijka dla tych, którzy chcą łatwo kopiować i wklejać:

for run in {1..10}; do command; done

19
Jeśli masz wiele iteracji, formularz z for (n=0;n<k;n++))może być lepszy; Podejrzewam, {1..k}że zmaterializuje łańcuch ze wszystkimi liczbami całkowitymi oddzielonymi spacjami.
Joe Koberg

@Joe Koberg, dzięki za wskazówkę. Zazwyczaj używam N <100, więc wydaje się to dobre.
bstpierre

10
@bstpierre: Formularz rozwinięcia nawiasu nie może używać zmiennych (łatwo) do określenia zakresu w Bash.
Wstrzymano do odwołania.

5
To prawda. Rozwinięcie nawiasów jest wykonywane przed rozwinięciem zmiennej zgodnie z gnu.org/software/bash/manual/bashref.html#Brace-Expansion , dlatego nigdy nie zobaczy wartości żadnych zmiennych.
Joe Koberg,

5
Smutna rzecz o zmiennej ekspansji. Używam następujących do zapętlenia n-razy i sformatowałem liczby: n=15;for i in $(seq -f "%02g" ${n});do echo $i; done 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
user1830432

203

Używając stałej:

for ((n=0;n<10;n++)); do some_command; done

Używanie zmiennej (może obejmować wyrażenia matematyczne):

x=10; for ((n=0; n < (x / 2); n++)); do some_command; done

4
Jest to najbliższa droga do języka programowania ^^ - powinna być zaakceptowana!
Nam G VU

Ten jest lepszy, ponieważ jest to pojedyncza linia, a zatem łatwiejszy w użyciu w terminalu
Kunok

Możesz także użyć zmiennej, np.x=10 for ((n=0; n < $x; n++));
Jelphy

@Kunok To właśnie za dopuszczalne, aby uruchomić zaakceptowanej odpowiedzi w jednym wierszu:for run in {1..10}; do echo "on run $run"; done
Douglas Adams

co jeśli nustawiono już określoną wartość? for (( ; n<10; n++ ))robi praca / edit: chyba najlepiej użyć jednego z innych odpowiedzi, takich jak while (( n++...jeden
phil294

121

Kolejny prosty sposób na włamanie:

seq 20 | xargs -Iz echo "Hi there"

uruchom echo 20 razy.


Zauważ, seq 20 | xargs -Iz echo "Hi there z"że wyświetli się:

Cześć 1
Cześć 2
...


4
nie potrzebujesz nawet 1 (można po prostu biegać seq 20)
Oleg Waszkiewicz

16
wersja 2015:seq 20 | xargs -I{} echo "Hi there {}"
mitnk

4
Pamiętaj, że znie jest to dobry symbol zastępczy, ponieważ jest tak powszechny, że może być zwykłym tekstem w tekście polecenia. Zastosowanie {}będzie lepsze.
mitnk

4
Jakiś sposób, aby odrzucić numer seq? więc po prostu wydrukowałby Hi there 20 razy (bez numeru)? EDYCJA: wystarczy użyć, -I{}a następnie nie mieć żadnych {}w poleceniu
Mike Graf

1
Po prostu użyj czegoś, jesteś pewien, że nie będzie tego w danych wyjściowych, na przykład IIIIIwtedy ładnie wygląda na przykład:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
rubo77

79

Jeśli używasz powłoki zsh:

repeat 10 { echo 'Hello' }

Gdzie 10 to liczba powtórzeń polecenia.


11
Chociaż nie jest to odpowiedź na pytanie (ponieważ wyraźnie wspomina o bash), jest bardzo przydatna i pomogła mi rozwiązać mój problem. +1
OutOfBound,

2
Ponadto, jeśli wpiszesz go w terminalu, możesz użyć czegoś takiegorepeat 10 { !! }
Renato Gomes

28

Korzystając z GNU Parallel możesz:

parallel some_command ::: {1..1000}

Jeśli nie chcesz, aby liczba była argumentem, i uruchamiaj tylko jedno zadanie na raz:

parallel -j1 -N0 some_command ::: {1..1000}

Obejrzyj film wprowadzający, aby zapoznać się z krótkim wprowadzeniem: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Przejrzyj samouczek ( http://www.gnu.org/software/parallel/parallel_tutorial.html ). Wiersz poleceń z miłością za to.


Dlaczego miałbyś używać narzędzia, którego prawdziwym powodem istnienia, jak to wyraźnie potwierdza jego nazwa, jest równoległe uruchamianie różnych rzeczy , a następnie przekazywanie tajemniczych argumentów, -j1 -N0które wyłączają równoległość ????
Ross Presser

@RossPresser To bardzo rozsądne pytanie. Powodem jest to, że łatwiej jest wyrazić to, co chcesz zrobić, używając składni GNU Parallel. Pomyśl: Jak działałbyś some_command1000 razy szeregowo bez argumentów? I: Czy możesz wyrazić to krócej parallel -j1 -N0 some_command ::: {1..1000}?
Ole Tange

Cóż, myślę, że masz trochę racji, ale nadal naprawdę masz wrażenie, że używasz precyzyjnie wykonanego bloku silnika jako młota. Gdybym był wystarczająco zmotywowany i czułem się z szacunkiem dla moich przyszłych następców próbujących zrozumieć, co zrobiłem, prawdopodobnie zawinę zamieszanie w skrypcie powłoki i po prostu go wywołam repeat.sh repeatcount some_command. Do implementacji w skrypcie powłoki, którego mógłbym użyć parallel, gdyby był już zainstalowany na komputerze (prawdopodobnie nie byłby). Lub mogę skłaniać się ku xargsom, ponieważ wydaje się to być najszybsze, gdy przekierowanie nie jest potrzebne some_command.
Ross Presser

Przepraszam, że moje „bardzo rozsądne pytanie” zostało wyrażone w nieuzasadniony sposób.
Ross Presser

1
W tym konkretnym przypadku może nie być to tak oczywiste. Staje się to bardziej oczywiste, jeśli użyjesz większej liczby opcji GNU Parallel, np. Jeśli chcesz powtórzyć nieudane polecenia 4 razy i zapisać dane wyjściowe w różnych plikach:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
Ole Tange

18

Prosta funkcja w pliku konfiguracyjnym bash ( ~/.bashrcczęsto) może działać dobrze.

function runx() {
  for ((n=0;n<$1;n++))
    do ${*:2}
  done
}

Nazwij to tak.

$ runx 3 echo 'Hello world'
Hello world
Hello world
Hello world

1
Lubię to. Gdyby można to było anulować za pomocą ctrl + c, byłoby świetnie
slashdottir

Dlaczego używanie tego sposobu do echa $ RANDOM n razy wyświetla ten sam numer?
BladeMight

3
To działa idealnie. Jedyną rzeczą, którą musiałem zmienić, to po prostu do ${*:2}dodałem eval, do eval ${*:2}aby upewnić się, że zadziała w przypadku uruchamiania poleceń, które nie zaczynają się od plików wykonywalnych. Na przykład, jeśli chcesz ustawić zmienną środowiskową przed uruchomieniem polecenia takiego jak SOME_ENV=test echo 'test'.
5_nd_5

12

xargsjest szybki :

#!/usr/bin/bash
echo "while loop:"
n=0; time while (( n++ < 10000 )); do /usr/bin/true ; done

echo -e "\nfor loop:"
time for ((n=0;n<10000;n++)); do /usr/bin/true ; done

echo -e "\nseq,xargs:"
time seq 10000 | xargs -I{} -P1 -n1 /usr/bin/true

echo -e "\nyes,xargs:"
time yes x | head -n10000 |  xargs -I{} -P1 -n1 /usr/bin/true

echo -e "\nparallel:"
time parallel --will-cite -j1 -N0 /usr/bin/true ::: {1..10000}

W nowoczesnym 64-bitowym systemie Linux zapewnia:

while loop:

real    0m2.282s
user    0m0.177s
sys     0m0.413s

for loop:

real    0m2.559s
user    0m0.393s
sys     0m0.500s

seq,xargs:

real    0m1.728s
user    0m0.013s
sys     0m0.217s

yes,xargs:

real    0m1.723s
user    0m0.013s
sys     0m0.223s

parallel:

real    0m26.271s
user    0m4.943s
sys     0m3.533s

Ma to sens, ponieważ xargspolecenie jest pojedynczym rodzimym procesem, który odradza /usr/bin/truepolecenie wiele razy, zamiast pętli fori whileinterpretowanych w Bash. Oczywiście działa to tylko dla jednego polecenia; jeśli musisz wykonać wiele poleceń w każdej iteracji w pętli, będzie to tak samo szybkie, a może szybsze, niż przekazywanie sh -c 'command1; command2; ...'do xargs

Można -P1to również zmienić, powiedzmy, -P8aby spawnować 8 procesów równolegle, aby uzyskać kolejny duży wzrost prędkości.

Nie wiem, dlaczego GNU równolegle jest tak wolne. Myślałem, że będzie to porównywalne z xargs.


1
GNU Parallel wykonuje sporo konfiguracji i księgowości: obsługuje programowalne ciągi zastępcze, buforuje dane wyjściowe, więc dane wyjściowe z dwóch równoległych zadań nigdy nie są mieszane, obsługuje kierunek (nie można wykonać: xargs "echo> foo"), a ponieważ nie jest napisany w bash musi odrodzić nową powłokę, aby dokonać przekierowania. Główną przyczyną jest jednak moduł Perla IPC :: open3 - więc każda praca nad uzyskaniem tego szybciej spowoduje szybsze GNU Parallel.
Ole Tange


7

Po pierwsze możesz zawinąć to w funkcję:

function manytimes {
    n=0
    times=$1
    shift
    while [[ $n -lt $times ]]; do
        $@
        n=$((n+1))
    done
}

Nazwij to tak:

$ manytimes 3 echo "test" | tr 'e' 'E'
tEst
tEst
tEst

2
To nie zadziała, jeśli polecenie do uruchomienia jest bardziej złożone niż proste polecenie bez przekierowań.
chepner

Możesz sprawić, że będzie działać w bardziej złożonych przypadkach, ale może być konieczne zawinięcie polecenia w funkcję lub skrypt.
bta

2
trTu wprowadza w błąd i to działa na wyjściu manytimes, nie echo. Myślę, że powinieneś usunąć go z próbki.
Dmitry Ginzburg,

7

xargs i seq pomogą

function __run_times { seq 1 $1| { shift; xargs -i -- "$@"; } }

widok :

abon@abon:~$ __run_times 3  echo hello world
hello world
hello world
hello world

2
co robi Shift i podwójny myślnik w poleceniu? Czy „zmiana” jest naprawdę konieczna?
sebs

2
To nie zadziała, jeśli polecenie jest bardziej złożone niż proste polecenie bez przekierowań.
chepner

Bardzo wolno, echo $ RANDOM 50 razy zajęło 7 sekund, a mimo to wyświetla tę samą liczbę losową przy każdym uruchomieniu ...
BladeMight


5

Jeśli możesz robić to okresowo, możesz uruchomić następującą komendę, aby uruchomić ją co 1 sekundę w nieskończoność. Możesz wprowadzić inne niestandardowe kontrole, aby uruchomić je n razy.

watch -n 1 some_command

Jeśli chcesz mieć wizualne potwierdzenie zmian, dołącz --differencesprzed lspoleceniem.

Według strony podręcznika OSX jest też

Opcja --cumulative sprawia, że ​​podświetlanie jest „lepkie”, co pokazuje bieżące wyświetlanie wszystkich pozycji, które kiedykolwiek się zmieniły. Opcja -t lub --no-title wyłącza nagłówek pokazujący interwał, polecenie i aktualny czas w górnej części wyświetlacza, a także następujący pusty wiersz.

Strona podręcznika Linux / Unix można znaleźć tutaj


5

Rozwiązałem tę pętlę, w której powtórzenie jest liczbą całkowitą reprezentującą liczbę pętli

repeat=10
for n in $(seq $repeat); 
    do
        command1
        command2
    done

4

Jeszcze inna odpowiedź: użyj rozwinięcia parametru dla pustych parametrów:

# calls curl 4 times 
curl -s -w "\n" -X GET "http:{,,,}//www.google.com"

Testowane na Centos 7 i MacOS.


To interesujące, czy możesz podać więcej szczegółów na temat tego, dlaczego to działa?
Hassan Mahmud

1
Uruchamia rozszerzenie parametrów n razy, ale ponieważ parametr jest pusty, tak naprawdę nie zmienia tego, co jest uruchamiane
jcollum

Poszukiwanie rozszerzenia parametrów i zwijania nie doprowadziło do żadnych wyników. Ledwo wiem, jak się zwijają. Byłoby wspaniale, gdybyś mógł wskazać mi jakiś artykuł lub inną wspólną nazwę tej funkcji. Dzięki.
Hassan Mahmud

1
Myślę, że to może pomóc gnu.org/software/bash/manual/html_node/…
jcollum

3

Wszystkie istniejące odpowiedzi wydają się wymagać bashi nie działają ze standardowym BSD UNIX /bin/sh(np. W kshOpenBSD ).

Poniższy kod powinien działać na każdym BSD:

$ echo {1..4}
{1..4}
$ seq 4
sh: seq: not found
$ for i in $(jot 4); do echo e$i; done
e1
e2
e3
e4
$

2

Trochę naiwne, ale tak zwykle pamiętam z głowy:

for i in 1 2 3; do
  some commands
done

Bardzo podobny do odpowiedzi @ Joe-Koberg. Jego jest lepszy, zwłaszcza jeśli potrzebujesz wielu powtórzeń, tylko trudniej mi zapamiętać inną składnię, ponieważ w ostatnich latach nie używam bashdużo. Mam na myśli, że przynajmniej nie do pisania skryptów.


1

Plik skryptu

bash-3.2$ cat test.sh 
#!/bin/bash

echo "The argument is  arg: $1"

for ((n=0;n<$1;n++));
do
  echo "Hi"
done

i wynik poniżej

bash-3.2$  ./test.sh 3
The argument is  arg: 3
Hi
Hi
Hi
bash-3.2$


0

Pętle są prawdopodobnie właściwym sposobem na zrobienie tego, ale jest fajna alternatywa:

echo -e {1..10}"\n" |xargs -n1 some_command

Jeśli potrzebujesz numeru iteracji jako parametru do wywołania, użyj:

echo -e {1..10}"\n" |xargs -I@ echo now I am running iteration @

Edytować: słusznie skomentowano, że powyższe rozwiązanie będzie działać płynnie tylko przy prostych uruchomieniach poleceń (bez potoków itp.). zawsze możesz użyćsh -c bardziej skomplikowanych rzeczy, ale nie warto.

Inną metodą, której zwykle używam, jest następująca funkcja:

rep() { s=$1;shift;e=$1;shift; for x in `seq $s $e`; do c=${@//@/$x};sh -c "$c"; done;}

teraz możesz to nazwać:

rep 3 10 echo iteration @

Pierwsze dwie liczby określają zakres. @Będą tłumaczone na liczbę iteracji. Teraz możesz używać tego również z rurami:

rep 1 10 "ls R@/|wc -l"

z poda liczbę plików w katalogach R1 .. R10.


1
To nie zadziała, jeśli polecenie jest bardziej złożone niż proste polecenie bez przekierowań.
chepner

w porządku, ale zobacz moją edycję powyżej. Edytowana metoda używa sh-c, więc powinna również działać z przekierowaniem.
stacksia 16.09.13

rep 1 2 touch "foo @"nie utworzy dwóch plików „foo 1” i „foo 2”; raczej tworzy trzy pliki „foo”, „1” i „2”. Może istnieć sposób na poprawne cytowanie za pomocą printfi %q, ale trudne będzie uzyskanie dowolnej sekwencji słów poprawnie cytowanych w jednym ciągu, który należy przekazać sh -c.
chepner

OP poprosił o bardziej zwięzłe , więc dlaczego dodajesz coś takiego, skoro jest już 5 zwięzłych odpowiedzi?
zoska

Po zdefiniowaniu definicji repw twoim .bashrc, kolejne wywołania stają się o wiele bardziej zwięzłe.
musiphil
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.