É possível realizar a substituição do comando shell sem usar uma subshell?

10

Eu tenho um cenário que exige a substituição de comandos sem usar um subshell. Eu tenho um constructo como este:

pushd $(mktemp -d)

Agora quero sair e remover o diretório temporário de uma só vez:

rmdir $(popd)

No entanto, isso não funciona porque popd não retorna o diretório popped (ele retorna o diretório novo, agora atual) e também porque é executado em um subshell.

Algo como

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

retornará o diretório popado, mas não pode ser usado assim:

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

porque o popd afetará apenas a subzela. O que é necessário é a capacidade de fazer isso:

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

mas essa é uma sintaxe inválida. É possível conseguir esse efeito?

(nota: Eu sei que posso salvar o diretório temporário em uma variável; eu estava tentando evitar a necessidade de fazer isso e aprender algo novo no processo!)

    
por starfry 03.08.2016 / 16:13

3 respostas

10

A escolha do título da sua pergunta é um pouco confusa.

pushd / popd , um recurso csh copiado por bash e zsh , são uma maneira de gerenciar uma pilha de diretórios lembrados.

pushd /some/dir

empurra o diretório de trabalho atual para uma pilha, e então altera o diretório de trabalho atual (e então imprime /some/dir seguido pelo conteúdo dessa pilha separados).

popd

imprime o conteúdo da pilha (novamente, separado por espaço) e, em seguida, muda para o elemento superior da pilha e a salta da pilha.

(também cuidado com o fato de que alguns diretórios serão representados lá com sua ~/x ou ~user/x notação).

Portanto, se a pilha tiver atualmente /a e /b , o diretório atual será /here e você estará executando:

 pushd /tmp/whatever
 popd

pushd imprimirá /tmp/whatever /here /a /b e popd produzirá /here /a /b , não /tmp/whatever . Isso é independente do uso de substituição de comandos ou não. popd não pode ser usado para obter o caminho do diretório anterior e, em geral, sua saída não pode ser processada (consulte o array $dirstack or $DIRSTACK de alguns shells para acessar os elementos dessa pilha de diretório)

Talvez você queira:

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

Ou

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

No entanto, eu usaria:

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

Em qualquer caso, pushd "$(mktemp -d)" não executa pushd em um subshell. Se assim fosse, não poderia alterar o diretório de trabalho. Isso é mktemp que é executado em um subshell. Como é um comando separado, ele precisa ser executado em um processo separado. Ele grava sua saída em um pipe e o processo do shell o lê na outra extremidade do pipe.

O ksh93 pode evitar o processo separado quando o comando está embutido, mas mesmo assim, ele ainda é um subshell (um ambiente de trabalho diferente) que desta vez é emulado, em vez de depender do ambiente normalmente fornecido pelo bifurcação. Por exemplo, em ksh93 , a=0; echo "$(a=1; echo test)"; echo "$a" , nenhum fork está envolvido, mas ainda echo "$a" outputs 0 .

Aqui, se você quiser armazenar a saída de mktemp em uma variável, ao mesmo tempo em que a passar para pushd , com zsh , poderá fazer:

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

Com outras shells parecidas com Bourne:

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

Ou para usar a saída de $(mktemp -d) várias vezes sem armazená-lo explicitamente em uma variável, você poderia usar zsh funções anônimas:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"
    
por 03.08.2016 / 16:35
2

Você pode desvincular o diretório primeiro, antes de sair:

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

ou

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

mas observe que pushd e popd são realmente ferramentas para shells interativos, não para scripts (é por isso que eles são muito faladores; comandos de script reais são silenciosos quando eles são bem-sucedidos).

    
por 03.08.2016 / 18:01
0

No bash, dirs fornece uma lista de diretórios lembrados pelo método pushd / popd.

Além disso, dirs -1 imprime o último diretório incluído na lista.

Então, para remover o diretório criado anteriormente executando pushd $(mktmp -d) , use:

rmdir $(dirs -1)

Em seguida, popd o diretório já removido da lista:

popd > /dev/null

Tudo em uma linha:

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

E adicionando a opção (-l) para evitar a notação ~/x ou ~user/x :

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

Que é notavelmente semelhante à linha que você pediu.
Exceto que eu não usaria &> , pois esconderia qualquer relatório de erro por popd .

Nota: O diretório permanecerá após rmdir , pois é o pwd nesse ponto. E será realmente desvinculado após o comando popd (sem links remanescentes usados).

Existe uma opção para usar, para shells que suportam a variável "OLDPWD" (a maioria das shells parecidas com bourne: ksh, bash, zsh tem $OLDPWD ). É interessante notar que ksh não implementa dirs, pod, pushd por padrão (lksh, dash e outros também don ' t tem popd disponível, portanto, não é utilizável aqui):

popd && rmdir "$OLDPWD"

Ou mais idiomático (a mesma lista de shells acima):

popd && rmdir ~-
    
por 04.08.2016 / 06:34