Jak mogę warunkowo przekazać podpowłokę przez „czas”?

9

Mam skrypt instalacyjny dla pudełka Vagrant, w którym mierzyłem pojedyncze kroki time. Teraz chciałbym warunkowo włączyć lub wyłączyć pomiary czasu.

Na przykład wcześniej linia wyglądałaby tak:

time (apt-get update > /tmp/last.log 2>&1)

Teraz pomyślałem, że mogę po prostu zrobić coś takiego:

MEASURE_TIME=true
[[ $MEASURE_TIME = true ]] && TIME="time --format=%e" || TIME=""

$TIME (apt-get update > /tmp/last.log 2>&1)

Ale to nie zadziała:

syntax error near unexpected token `apt-get'
`$TIME (apt-get update > /tmp/last.log 2>&1)'

W czym problem?

Der Hochstapler
źródło
3
Musisz tylko osiągnąć docelową prędkość, aby kondensator topnika zadziałał;)
złotowłosa
2
@Goldilocks Masz na myśli magnes ? ;)
CVn

Odpowiedzi:

13

Aby móc określić czas podpowłoki, potrzebujesz time słowa kluczowego , a nie polecenia.

timeSłów kluczowych, częścią języka, jest uznawane tylko jako takie, gdy wszedł dosłownie i jako pierwsze słowo polecenia (aw przypadku ksh93, a następny znak nie startuje z -). Nawet wejście "time"nie zadziała, nie mówiąc już o tym $TIME(a timezamiast tego zostanie odebrane jako wywołanie polecenia).

Możesz użyć tutaj aliasów, które są rozwinięte przed wykonaniem kolejnej rundy analizowania (aby powłoka rozpoznała to timesłowo kluczowe):

shopt -s expand_aliases
alias time_or_not=
TIMEFORMAT=%E

MEASURE_TIME=true
[[ $MEASURE_TIME = true ]] && alias time_or_not=time

time_or_not (apt-get update > /tmp/last.log 2>&1)

Słowo time kluczowe nie przyjmuje opcji (z wyjątkiem -pin bash), ale format można ustawić za pomocą TIMEFORMATzmiennej inbash . ( shoptczęść jest również bashspecyficzna, inne powłoki na ogół tego nie potrzebują).

Stéphane Chazelas
źródło
Powiedziałeś „Aby mieć czas na podpowłokę, potrzebujesz słowa kluczowego time”. Zastanawiam się, czy to zachowanie udokumentowało gdzieś?
cuonglm,
@cuonglm, patrzinfo -f bash --index-search=time
Stéphane Chazelas,
3

Chociaż jest aliasto jeden ze sposobów, można to zrobić evalrównież za pomocą - po prostu nie tyle chcesz evalwykonania polecenia, ile chceszeval deklaracji polecenia .

podoba mi się alias es - używam ich cały czas, ale bardziej lubię funkcje - szczególnie ich zdolność do obsługi parametrów i że niekoniecznie muszą być one rozszerzane w pozycji poleceń, jak jest to wymagane dla aliases.

Pomyślałem więc, że może też chcesz spróbować:

_time() if   set -- "${IFS+IFS=\$2;}" "$IFS" "$@" && IFS='
';      then set -- "$1" "$2" "$*"; unset IFS
             eval "$1 $TIME ${3#"$1"?"$2"?}"
        fi

Chodzi $IFSgłównie o to $*. Ważne jest, aby wynik ( subshell bit )był również wynikiem rozszerzenia powłoki, a więc w celu rozwinięcia argumentów w parsowalny ciąg, którego używam "$*" (nie eval "$@", nawiasem mówiąc, chyba że jesteś pewien, że wszystkie argumenty można połączyć spacjami) . Rozdzielony separator między argumentami "$*"jest pierwszym bajtem wejściowym $IFS, więc kontynuowanie pracy bez pewności co do jej wartości może być niebezpieczne. Tak więc funkcja zapisuje $IFS, ustawia ją na \newline wystarczająco długo, aby set ... "$*"w "$3", unsets, a następnie resetuje swoją wartość, jeśli wcześniej miała taką.

Oto małe demo:

TIME='set -x; time'
_time \( 'echo "$(echo any number of subshells)"' \
         'command -V time'                        \
         'hash time'                              \
      \) 'set +x'

Widzisz, umieściłem w poleceniu dwie wartości $TIME- dowolna liczba jest w porządku - nawet żadna - ale upewnij się, że została poprawnie zmieniona i zacytowana - i to samo dotyczy argumentów _time(). Wszystkie zostaną połączone w jeden ciąg komend, gdy zostaną wykonane - ale każdy argument otrzymuje własny \newline, dzięki czemu można je stosunkowo łatwo rozłożyć. W przeciwnym razie możesz zebrać je wszystkie w jednym, jeśli chcesz, i rozdzielić je na \newline lub średniki lub co tam masz. Po prostu upewnij się, że pojedynczy argument reprezentuje polecenie, które możesz swobodnie umieścić w swoim wierszu w skrypcie podczas jego wywoływania.\(, na przykład jest w porządku, o ile w końcu zostanie zastosowane \). Zasadniczo normalne rzeczy.

Po evalotrzymaniu powyższego fragmentu wygląda on następująco:

+ eval 'IFS=$2;set -x; time (
echo "$(echo any number of subshells)"
command -V time
hash time
)
set +x'

A jego wyniki wyglądają jak ...

WYNIK

+++ echo any number of subshells
++ echo 'any number of subshells'
any number of subshells
++ command -V time
time is a shell keyword
++ hash time
bash: hash: time: not found

real    0m0.003s
user    0m0.000s
sys     0m0.000s
++ set +x

hashBłąd wskazuje, że nie masz /usr/bin/timezainstalowanego (bo nie) i commandniech nas nie wie czym jest uruchomiony. Trailing set +xjest kolejną komendą wykonywaną po time (co jest możliwe) - ważne jest, aby zachować ostrożność przy komendach wejściowych podczas evalpisania czegokolwiek.

mikeserv
źródło
Czy jest jakiś powód, aby tego nie robić _time() { eval "$TIME $@"; }? Korzystanie z funkcji ma tę wadę, że wprowadza inny zakres dla zmiennych (nie jest to problem dla podpowłoki)
Stéphane Chazelas
@ StéphaneChazelas - ze względu na cytaty w "$@"rozszerzeniu. Lubię jednego argumentu eval- w przeciwnym razie robi się strasznie. Ummm .... co masz na myśli mówiąc o innym zakresie? Myślałem, że to tylko functionfunkcje ...
Mikeserv
evaldołącza do argumentów przed wykonaniem, co próbujesz zrobić w bardzo zawiły sposób. Miałem na myśli, _time 'local var=1; blah'że uczyni to varlokalnym lokalnym _timelub _time 'echo "$#"'wydrukuje $#_timefunkcję, a nie wywołującego.
Stéphane Chazelas,
@ StéphaneChazelas - Mam tutaj dwie kompletki zakładek. Zgadza się - evalkonkluduje swoje argumenty na przestrzeniach - co, jak mówisz, jest dokładnie tym, co tutaj robię - chociaż nie było to początkowe zamierzenie. Naprawię to. evaling "$*"w przeciwieństwie do "$@"to nawyk Podniosłam po wielu run-in jest ze command not foundkiedy args zostały połączone w niewłaściwym miejscu. W każdym razie, chociaż argumenty są ostatecznie wykonywane w funkcji, myślę, że wystarczy je rozwinąć przy wywołaniu. Tak czy "$#"inaczej bym zrobił .
mikeserv
+1 za fajny pokręcony hackery ...
Stéphane Chazelas