Como posso empurrar uma série de diretórios de uma só vez?

2

Estou no diretório foo , que contém os subdiretórios bar1 bar2 e bar3 e nada mais.

Eu gostaria de atuar pushd em foo , bar1 , bar2 e bar3 em um comando, mas estou encontrando dificuldades:

find 'pwd' | xargs pushd

retorna

xargs: pushd: No such file or directory

Eu tentei contornar o problema com um loop while:

find 'pwd' | while read line ; do pushd $line ; done

O que dá uma saída que parece correta:

~/foo ~/foo
~/foo/bar1 ~/foo ~/foo
~/foo/bar2 ~/foo/bar1 ~/foo ~/foo
~/foo/bar3 ~/foo/bar2 ~/foo/bar1 ~/foo ~/foo

No entanto, usar dirs depois disso mostra que não adicionei novos diretórios à pilha:

~/foo

Alguém consegue identificar o que estou fazendo errado?

    
por Miguel 22.06.2017 / 11:59

2 respostas

1

Você está muito perto. Você pode fazer o que quiser com

while read line; do pushd "$line"; done < <(find "$(pwd)" -type d)

O problema com o seu comando é que pushd , como cd , deve ser feito no processo principal (pai) para ser útil, e (com alguma variação baseada em como seu sistema está configurado), os comandos em um pipeline são executados em subprocessos (processos filho). < <(cmd) magicamente te dá um cachimbo sem lhe dar um pipeline.

Isso requer que você esteja executando o bash (ou talvez um dos outros shells avançados?), já que o POSIX não suporta <(cmd) .

A abordagem xargs , infelizmente, estava fadada ao fracasso porque pushd (como cd ) é um comando interno do shell (isto é, não existe nenhum programa chamado pushd ), e xargs requer um programa externo e executável. Você pode obter uma saída que (quase) parece correta com este comando:

$ find "$(pwd)" -type d | xargs -i sh -c 'pushd "$1"' sh
~/foo ~/foo
~/foo/bar1 ~/foo
~/foo/bar2 ~/foo
~/foo/bar3 ~/foo

mas que está executando o shell como um programa externo, e fazendo isso (independentemente) para cada diretório. Isso é um pouco mais perto:

$ find "$(pwd)" -type d | xargs sh -c 'for line do pushd "$line"; done' sh
~/foo ~/foo
~/foo/bar1 ~/foo ~/foo
~/foo/bar2 ~/foo/bar1 ~/foo ~/foo
~/foo/bar3 ~/foo/bar2 ~/foo/bar1 ~/foo ~/foo

executando um único processo de shell, e dizendo para fazer um loop em todos os diretórios. Como você pode ver, esta técnica é semelhante à sua segunda tentativa (e minha resposta), e o resultado é o mesmo da sua segunda tentativa - você recebe um novo processo de shell que chama pushd quatro vezes, e acaba com uma pilha de diretórios com cinco níveis de profundidade (contando o diretório inicial duas vezes) - mas esse novo processo de shell está em um subprocesso, e, quando você vir o próximo prompt do shell, esse processo do shell desapareceu.

Para referência, note que este comando é um pouco semelhante ao uma resposta que dei há alguns anos . Stéphane Chazelas discute a construção do comando sh -c long_complex_shell_command sh aqui e aqui .

    
por 22.06.2017 / 12:15
1

Este não é um trabalho para find .

for subdirectory in */; do
  pushd -- "$subdirectory"
done

Seu código não funciona porque pushd , como cd , precisa ser executado no ambiente de shell que você deseja afetar. Não existe nenhum comando externo pushd que xargs possa chamar porque um comando externo seria inútil: não afetaria nada. O lado direito de um pipe é executado em um subshell (em seu shell, há shells em que ele é executado em seu outro shell e sua segunda tentativa funcionaria); você pode ver pushd trabalhando com

find 'pwd' | {
  while read line ; do pushd $line ; done;
  dirs
}

Trabalhar a menos que os nomes dos diretórios contenham caracteres especiais, ou seja. Sempre coloque aspas duplas na variável substituições, a menos que você saiba por que precisa deixá-las .

    
por 23.06.2017 / 04:43