Можно ли выполнить подстановку команд оболочки без использования подоболочки?

11

У меня есть сценарий, который требует подстановки команд без использования подоболочки. У меня есть такая конструкция:

pushd $(mktemp -d)

Теперь я хочу выйти и удалить временный каталог за один раз:

rmdir $(popd)

Однако это не работает, потому popdчто не возвращает перенесенный каталог (он возвращает новый, теперь текущий, каталог), а также потому, что он выполняется в подоболочке.

Что-то типа

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

вернет извлеченный каталог, но его нельзя использовать так:

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

потому что popdбудет влиять только на подоболочку. Для этого требуется способность сделать это:

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

но это неверный синтаксис. Возможно ли добиться этого эффекта?

(примечание: я знаю, что могу сохранить временный каталог в переменной; я пытался избежать необходимости делать это и учиться чему-то новому в процессе!)

starfry
источник
1
Я бы сохранил каталог в переменную; таким образом, trapобработчик может очистить каталог, если процесс будет обработан сигналом.
thrig
Ответ - нет .
h0tw1r3
Похоже на on fish, эквивалент подстановки команд (), изменяет папку внешней оболочки. Это обычно раздражает, но в таких случаях это полезно, я уверен.
trysis

Ответы:

10

Выбор названия вашего вопроса немного сбивает с толку.

pushd/ popd, cshфункция, скопированная bashи zsh, является способом управления стеком запомненных каталогов.

pushd /some/dir

помещает текущий рабочий каталог в стек, а затем изменяет текущий рабочий каталог (а затем печатает, /some/dirа затем содержимое этого стека (через пробел).

popd

печатает содержимое стека (опять же разделенное пробелами), а затем переключается на верхний элемент стека и извлекает его из стека.

(также помните, что некоторые каталоги будут представлены там с их ~/xили ~user/xобозначениями).

Так что если в стеке в данный момент есть /aи /b, текущим каталогом является /hereи вы работаете:

 pushd /tmp/whatever
 popd

pushdбудет печатать /tmp/whatever /here /a /bи popdвыводить /here /a /b, а не /tmp/whatever. Это не зависит от использования подстановки команд или нет. popdне может использоваться для получения пути к предыдущему каталогу, и, как правило, его выходные данные не могут быть обработаны после обработки (см. массив $dirstackили $DIRSTACKнекоторые оболочки, хотя для доступа к элементам этого стека каталога)

Может быть, вы хотите:

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

Или

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

Хотя я бы использовал:

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

В любом случае, pushd "$(mktemp -d)"не работает pushdв подоболочке. Если это так, он не может изменить рабочий каталог. Это то, mktempчто работает в подоболочке. Поскольку это отдельная команда, она должна выполняться в отдельном процессе. Он записывает свой вывод в канал, а процесс оболочки читает его на другом конце канала.

ksh93 может избежать отдельного процесса, когда команда встроена, но даже там, это все еще подоболочка (другая рабочая среда), которая на этот раз эмулируется, а не полагается на отдельную среду, обычно предоставляемую разветвлением. Например, в ksh93, a=0; echo "$(a=1; echo test)"; echo "$a"вилка не задействована, но все равно echo "$a"выводится 0.

Здесь, если вы хотите сохранить выходные данные mktempв переменной, в то же время, когда вы передаете ее pushd, с помощью zsh, вы можете сделать:

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

С другими подобными Борну оболочками:

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

Или использовать вывод $(mktemp -d)несколько раз без явного сохранения его в переменной, вы можете использовать zshанонимные функции:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"
Стефан Шазелас
источник
Я понимаю pushdи popdработаю так, как вы описываете, и что они не зависят от подстановки команд - здесь нет путаницы! Тем не менее, вы ответили собственной проблемой, раскрывая $OLDPWD. Я могу сделать popd; rmdir $OLDPWD. Это ответ на мою проблему - все остальное только подтверждает то, что я думал. Подстановка команд, кажется, является способом ее решения, но это не из-за подоболочки, и вы не можете выполнить подстановку команд без подчиненной оболочки, так что спасибо за раскрытие OLDPWD - это именно то, что мне нужно!
звездный день
@starfry, rmdir $(popd)заставляет popdработать в под-оболочке, что означает, что он не изменит текущий каталог, но даже если он не был запущен в подоболочке, вывод popdне будет временным каталогом, это будет список, разделенный пробелами каталогов, не включая этот временный каталог. Вот где я говорю, что ты в замешательстве.
Стефан Шазелас
но мне показалось, что я сказал это в своем вопросе: «popd не возвращает перенесенный каталог (он возвращает новый, теперь текущий, каталог), а также потому, что он выполняется в подоболочке». По общему признанию это возвращает список, но первый в списке - тот, к которому я обращался.
звездный день
@starfry, хорошо, извини. Допустим, меня смутило название вопроса, а остальные я не прочитал достаточно внимательно.
Стефан Шазелас
хорошо, ваш ответ был все еще хорош и указал мне правильное направление. Я никогда не знал об этой переменной оболочки OLDPWD. Я должен помнить, чтобы один день читать man bashот начала до конца!
Звездный день
2

Вы можете отсоединить каталог, прежде чем покинуть его:

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

или

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

но обратите внимание , что pushdи popdна самом деле инструменты для интерактивных оболочек, а не для скриптов (именно поэтому они столь разговорчивы; команды реальных сценариев молчат , когда им удается).

Тоби Спейт
источник
0

В bash dirsпредоставьте список запомненных каталогов методом pushd / popd.

Также dirs -1печатает последний каталог, включенный в список.

Итак, чтобы удалить каталог, созданный ранее путем выполнения pushd $(mktmp -d), используйте:

rmdir $(dirs -1)

И тогда popdуже удаленный каталог из списка:

popd > /dev/null

Все в одной строке:

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

И добавив опцию (-l), чтобы избежать обозначения ~/xили ~user/x:

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

Что удивительно похоже на строку, которую вы просили.
За исключением того, что я не буду использовать, &>поскольку он будет скрывать любые сообщения об ошибках popd.

Примечание. Каталог останется после того, rmdirкак он будет pwdв этой точке. И будет фактически не связан после popdкоманды (оставшиеся ссылки не используются).


Существует возможность использовать для оболочек, которые поддерживают переменную «OLDPWD» (большинство оболочек типа bourne: ksh, bash, zsh have $OLDPWD). Интересно отметить, что ksh не реализует dirs, pod, pushd по умолчанию (lksh, dash и другие также не имеют popd, поэтому здесь их нельзя использовать):

popd && rmdir "$OLDPWD"

Или, более идиоматический (тот же список оболочек, что и выше):

popd && rmdir ~-
Сообщество
источник
Проблема с этим, и то, что я пытался решить, заключается в том, что вы не можете найти rmdirтекущий каталог, в котором вы находитесь, когда будете это делать. Вы должны выполнить действие, popdпрежде чем делать, rmdirно вам нужно знать, что удалить, и popdне говорит вам об этом. Я, как и вы, думал, dirs -l -1что ответ был, но с тех пор обнаружил, что ответ на самом деле использовать $OLDPWD.
звездный день
@starfry Я считаю , что самый короткий ответ на использование: popd && rmdir ~-.
@starfry Вы можете удалить текущий каталог, без проблем, при условии, что он пустой (без принудительного удаления). Вы могли бы даже позвонить rmdir "$PWD"все в порядке. Кроме того, текущий каталог должен быть тем, который был создан mktmp -d(если он pushd $(mktemp -d)был выполнен заранее) и в который pushdперемещается pwd. Итак, да, после pushd и до popd, созданный каталог сохраняется $(dirs -1), я не вижу никаких проблем.