2012-11-13 9 views
7

W bash skryptu Chciałbym przypisać wyjście times wbudowanego do zmiennej tablicy, ale nie znalazłem lepszego sposobu niżBash: przypisanie wyjścia wbudowanego polecenia "czasy do zmiennej

tempnam=/tmp/aaa_$$_$RANDOM 
times > ${tempnam} 
mapfile -t times_a < ${tempnam} 

Zapisuję dane wyjściowe do pliku tymczasowego i odczytuję je w tablicy times_a, ponieważ potoki lub $(times) byłyby wykonywane w podpowłoce i zwracałyby błędne wartości.

Jakieś lepsze rozwiązanie bez pliku tymczasowego?

Odpowiedz

6

Podstawowym problemem, który należy rozwiązać, jest wykonanie zarówno w jednej, jak i w drugiej powłoce, bez pliku tymczasowego, wykonania time. Niemal każda metoda Bash zapewnia wyprowadzanie wyjścia z jednej rzeczy do drugiej lub przechwytywanie wyniku polecenia, ma jedną stronę działającą w podpowłoce.

Oto jeden sposób można to zrobić bez pliku tymczasowego, ale będę cię ostrzec, to nie dość, że nie jest przenośny do innych pocisków, a to wymaga co najmniej Bash 4:

coproc co { cat; }; times 1>&${co[1]}; eval "exec ${co[1]}>&-"; mapfile -tu ${co[0]} times_a 

Załatwię to dla ciebie:

coproc co { cat; } 

To tworzy koproces; proces, który działa w tle, ale dostajesz rury do rozmowy z jego standardowym wejściem i standardowym wyjściem, które są FDs ${co[0]} (standard z cat) i (standard w cat). Polecenia są wykonywane w podpowłoce, więc nie możemy wykonać żadnego z naszych celów (uruchamianie times lub odczytywanie do zmiennej), ale możemy użyć cat, aby po prostu przekazać dane wejściowe do wyjścia, a następnie użyć tej rury rozmawiać z times i mapfile w bieżącej powłoce.

times >&${co[1]}; 

Run times, przekierowując swoje standardy z normą w polecenia w cat.

eval "exec ${co[1]}>&-" 

Close koniec wejście komendy cat. Jeśli tego nie zrobimy, cat będzie kontynuować oczekiwanie na dane wejściowe, pozostawiając otwarte dane wyjściowe, a mapfile będzie czekać na to, powodując zawieszenie się powłoki. exec, po przekazaniu bez poleceń, po prostu stosuje swoje przekierowania do bieżącej powłoki; przekierowanie do - zamyka FD. Musimy użyć eval, ponieważ Bash wydaje się mieć problem z exec ${co[1]}>&-, interpretując FD jako polecenie zamiast części przekierowania; użycie eval umożliwia podstawienie tej zmiennej najpierw, a następnie wykonanie.

mapfile -tu ${co[0]} times_a 

W końcu rzeczywiście odczytaliśmy dane z normy z koprocesu. W tej powłoce udało nam się uruchomić zarówno komendę times, jak i mapfile i nie używaliśmy plików tymczasowych, chociaż używaliśmy tymczasowego procesu jako potoku między tymi dwoma poleceniami.

Pamiętaj, że ma to subtelny wyścig. Jeśli wykonasz te komendy jeden po drugim, zamiast wszystkich jako jedno polecenie, ostatnie zawiedzie; ponieważ po zamknięciu standardowego wejścia kończy się, powodując zamknięcie koprocesora i zamknięcie FD. Wygląda na to, że po wykonaniu wszystkiego w jednym wierszu, mapfile jest wykonywany wystarczająco szybko, że koproces jest nadal otwarty, gdy działa, a zatem może odczytać z rury; ale mogę mieć szczęście. Nie wymyśliłem tego w dobry sposób.

Wszystko powiedziawszy, znacznie łatwiej jest po prostu napisać plik tymczasowy. użyłbym mktemp do wygenerowania pliku, a jeśli jesteś w skrypcie, dodać pułapkę, aby oczyścić swój pliku tymczasowego przed wyjściem:

tempnam=$(mktemp) 
trap "rm '$tempnam'" EXIT 
times > ${tempnam} 
mapfile -t times_a < ${tempnam} 
+1

Chciałbym upvote to nawet więcej, nauczyłem dużo czytając to, naprawdę fajna odpowiedź! – higuaro

0

Jedna możliwość zrobić coś podobnego byłoby

times > >(other_command) 

To przekierowanie wyjścia combided z substytucji procesowej. W ten sposób zostanie wykonana times w bieżącej powłoce, a wyjście zostanie przekierowane do nowego podprocesu. Dlatego jednak wykonanie tego z mapfile nie ma większego sensu, ponieważ polecenie to nie zostanie wykonane w tej samej powłoce, a to prawdopodobnie nie jest to, czego chcesz. Sytuacja jest nieco trudna, ponieważ nie można wywoływać wywołań wbudowanych powłoki w podpowłoce.

1

Wow, dobre pytanie.

Udoskonaleniem byłoby użycie mktemp, więc nie polegasz na losowości, aby zachować unikatowy plik.

TMPFILE=$(mktemp aaa_XXXXXXXXXX) 
times > "$TMPFILE" 
mapfile -t times_a < ${tempnam} 
rm "$TMPFILE" 

Używam również zamiast pliku mapy (ponieważ nie mam pliku mapy).

a=0; for var in $(cat "$TMPFILE"); do ((a++)); TIMES_A[$a]=$var; done 

Ale, tak, nie widzę w jaki sposób można to zrobić bez plików lub nazwanych potoków.

Powiązane problemy