Czy można wykonać podstawienie polecenia powłoki bez użycia podpowłoki?

11

Mam scenariusz, który wymaga zastąpienia poleceń bez użycia podpowłoki. Mam taki konstrukt:

pushd $(mktemp -d)

Teraz chcę wyjść i usunąć katalog tymczasowy za jednym razem:

rmdir $(popd)

Jednak to nie działa, ponieważ popdnie zwraca wyskakującego katalogu (zwraca nowy, teraz aktualny katalog), a także dlatego, że jest wykonywany w podpowłoce.

Coś jak

dirs -l -1 ; popd &> /dev/null

zwróci wyskakujący katalog, ale nie można go użyć w następujący sposób:

rmdir $(dirs -l -1 ; popd &> /dev/null)

ponieważ popdwpłynie tylko na podpowłokę. Potrzebna jest do tego zdolność:

rmdir { dirs -l -1 ; popd &> /dev/null; }

ale to nieprawidłowa składnia. Czy można osiągnąć ten efekt?

(uwaga: wiem, że mogę zapisać katalog tymczasowy w zmiennej; starałem się tego uniknąć i uczyłem się czegoś nowego!)

rozgwiazdy
źródło
1
Zapisałbym katalog do zmiennej; w ten sposób trapmoduł obsługi może wyczyścić katalog, jeśli proces zostanie zakłócony sygnałem.
thrig
Odpowiedź brzmi nie .
h0tw1r3
Wygląda na to fish, że jest to odpowiednik podstawiania poleceń (), zmienia folder zewnętrznej powłoki. Jest to zwykle denerwujące, ale w takich przypadkach jest to przydatne, jestem pewien.
trysis

Odpowiedzi:

10

Wybór tytułu pytania jest nieco mylący.

pushd/ popd, cshfunkcja skopiowana przez bashi zsh, jest sposobem zarządzania stosem zapamiętanych katalogów.

pushd /some/dir

wypycha bieżący katalog roboczy na stos, a następnie zmienia bieżący katalog roboczy (a następnie drukuje, /some/dira następnie zawartość tego stosu (rozdzieloną spacjami).

popd

drukuje zawartość stosu (ponownie, oddzielając spację), a następnie zmienia się na górny element stosu i wyskakuje ze stosu.

(strzeż się również, że niektóre katalogi będą tam reprezentowane wraz z ich notacją ~/xlub ~user/xzapisem).

Więc jeśli stos obecnie ma /ai /b, bieżący katalog to /herei działasz:

 pushd /tmp/whatever
 popd

pushdbędzie drukować /tmp/whatever /here /a /bi popdwyjście będzie /here /a /b, nie /tmp/whatever. Jest to niezależne od używania podstawiania poleceń, czy nie. popdnie można użyć do uzyskania ścieżki do poprzedniego katalogu i ogólnie jego dane wyjściowe nie mogą zostać przetworzone (patrz jednak tablica $dirstacklub $DIRSTACKniektórych powłok w celu uzyskania dostępu do elementów tego stosu katalogów)

Może chcesz:

pushd "$(mktemp -d)" &&
popd &&
rmdir "$OLDPWD"

Lub

cd "$(mktemp -d)" &&
cd - &&
rmdir "$OLDPWD"

Chociaż użyłbym:

tmpdir=$(mktemp -d) || exit
(
  cd "$tmpdir" || exit # in a subshell 
  # do what you have to do in that tmpdir
)
rmdir "$tmpdir"

W każdym razie pushd "$(mktemp -d)"nie działa pushdw podpowłoce. Jeśli tak, nie może zmienić katalogu roboczego. To mktempdziała w podpowłoce. Ponieważ jest to osobne polecenie, musi działać w osobnym procesie. Zapisuje dane wyjściowe na potoku, a proces powłoki odczytuje je na drugim końcu potoku.

ksh93 może uniknąć oddzielnego procesu, gdy polecenie jest wbudowane, ale nawet tam jest to podpowłoka (inne środowisko robocze), które tym razem jest emulowane, zamiast polegać na oddzielnym środowisku zapewnianym zwykle przez rozwidlenie. Na przykład, w ksh93, a=0; echo "$(a=1; echo test)"; echo "$a"żaden rozwidlenie nie jest zaangażowane, ale nadal echo "$a"generuje 0.

Tutaj, jeśli chcesz przechowywać dane wyjściowe mktempw zmiennej, w tym samym czasie jak przekazać go pushd, z zsh, można zrobić:

pushd ${tmpdir::="$(mktemp -d)"}

Z innymi pociskami podobnymi do Bourne'a:

unset tmpdir
pushd "${tmpdir=$(mktemp -d)}"

Lub, aby użyć wyniku $(mktemp -d)kilka razy bez wyraźnego przechowywania go w zmiennej, możesz użyć zshfunkcji anonimowych:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"
Stéphane Chazelas
źródło
Rozumiem pushdi popddziałam tak, jak to opisujesz i że są one niezależne od zastępowania poleceń - nie ma zamieszania! Jednak odpowiedziałeś własnym problemem ujawniając $OLDPWD. Mogę zrobić popd; rmdir $OLDPWD. To jest odpowiedź na mój problem - wszystko inne tylko potwierdza to, co myślałem. Wydaje się, że podstawianie poleceń jest sposobem na jego rozwiązanie, ale nie dzieje się tak z powodu podpowłoki i nie można wykonać podstawiania poleceń bez podpowłoki, więc dziękuję za ujawnienie OLDPWD - właśnie tego potrzebuję!
starfry
@starfry, rmdir $(popd)powoduje popduruchomienie w podpowłoce, co oznacza, że ​​nie zmieni bieżącego katalogu, ale nawet jeśli nie uruchomił się w podpowłoce, wyjściem popdnie będzie katalog tymczasowy, będzie to lista rozdzielona spacjami katalogów nie zawierających tej temp. reż. Mówię, że jesteś zdezorientowany.
Stéphane Chazelas
ale myślałem, że powiedziałem to w moim pytaniu: „popd nie zwraca wyskakującego katalogu (zwraca nowy, teraz aktualny katalog), a także dlatego, że jest wykonywany w podpowłoce”. Wprawdzie zwraca listę, ale pierwszą na liście jest ta, o której mówiłem.
starfry
@ starfry, OK przepraszam. Powiedzmy, że to ja byłem zdezorientowany tytułem pytania i nie przeczytałem wystarczająco uważnie reszty.
Stéphane Chazelas,
cóż, twoja odpowiedź była wciąż dobra i wskazała mi właściwy kierunek. Nigdy nie wiedziałem o tej zmiennej powłoki OLDPWD. Muszę pamiętać, aby któregoś dnia czytać man bashod końca do końca!
starfry
2

Możesz najpierw odłączyć katalog, zanim go opuścisz:

rmdir "$(pwd -P)" && popd

lub

rmdir "$(pwd -P)" && cd ..   # yes, .. is still usable

ale zauważmy, że pushdi popdtak naprawdę są narzędziami do interaktywnych powłok, a nie skryptów (dlatego są tak rozmowni; prawdziwe polecenia skryptowe milczą, gdy im się powiedzie).

Toby Speight
źródło
0

W bash dirspodaj listę zapamiętanych katalogów metodą pushd / popd.

Również dirs -1drukuje ostatni katalog zawarty w wykazie.

Tak więc, aby usunąć katalog utworzony wcześniej przez wykonanie pushd $(mktmp -d), użyj:

rmdir $(dirs -1)

A następnie popdjuż usunięty katalog z listy:

popd > /dev/null

Wszystko w jednym wierszu:

rmdir $(dirs -1); popd > /dev/null

I dodanie opcji (-l), aby uniknąć ~/xlub ~user/xnotacji:

rmdir $(dirs -l -1); popd > /dev/null

Co jest niezwykle podobne do linii, o którą prosiłeś.
Tyle że nie użyłbym &>tego, ponieważ ukryłoby to jakiekolwiek raportowanie błędów przez popd.

Uwaga: katalog pozostanie później, rmdirponieważ jest pwdw tym momencie. I będzie faktycznie rozłączony po popdkomendzie (żadne pozostałe łącza nie zostaną użyte).


Istnieje opcja użycia dla powłok obsługujących zmienną „OLDPWD” (większość powłok typu bourne: ksh, bash, zsh have $OLDPWD). Warto zauważyć, że ksh domyślnie nie implementuje katalogów, pod, pushd (lksh, dash i inne również nie mają popd dostępne, więc nie można ich tutaj używać):

popd && rmdir "$OLDPWD"

Lub bardziej idiomatyczny (ta sama lista muszli jak powyżej):

popd && rmdir ~-
Społeczność
źródło
Problem z tym i to, co próbowałem rozwiązać, polega na tym, że nie możesz rmdirw bieżącym katalogu, gdzie byłbyś, robiąc to. Musisz to popdzrobić przed zrobieniem, rmdirale musisz wiedzieć, co usunąć, i popdtego ci nie mówi. Ja, podobnie jak ty, myślałem, że dirs -l -1to odpowiedź, ale od tamtej pory odkryłem, że ta odpowiedź jest rzeczywiście przydatna $OLDPWD.
starfry
@starfry wierzę, że najkrótsza odpowiedź jest do stosowania: popd && rmdir ~-.
@starfry Możesz faktycznie usunąć bieżący katalog, bez problemu, pod warunkiem, że jest pusty (bez wymuszania wymazywania). Możesz nawet zadzwonić w rmdir "$PWD"porządku. Ponadto bieżący katalog powinien być katalogiem utworzonym przez mktmp -d(jeśli pushd $(mktemp -d)został wcześniej wykonany) i do którego pushdprzenosi pwd. Tak, po pushd i przed popd, utworzony katalog jest przechowywany w $(dirs -1), nie widzę żadnego problemu.