Mude o diretório e execute o comando automaticamente e então mude o diretório de volta

1

Estou tentando escrever um script que será executado em um determinado diretório com muitos subdiretórios de nível único. O script irá cd em cada um dos subdiretórios, executará um comando nos arquivos no diretório e cd retornará para o próximo diretório.

A saída deve ser retornada para novos diretórios com a mesma estrutura e nome do original. Qual é a melhor maneira de fazer isso?

    
por Alex 17.03.2016 / 17:14

2 respostas

2

A melhor maneira é usar subshells:

for dir in */; do
  (cd -- "$dir" && some command there)
done

Evite coisas como:

for dir in */; do
  cd -- "$dir" || continue
  some command there
  cd ..
done

ou:

here=$PWD
for dir in */; do
  cd -- "$dir" || continue
  some command here
  cd "$here"
done

Como eles são menos confiáveis, especialmente quando links simbólicos estão envolvidos ou o caminho para o diretório atual pode mudar enquanto você está executando o script.

A maneira "correta" de fazê-lo sem envolver um subshell seria abrir o diretório atual em um descritor de arquivo com o sinalizador close-on-exec, chdir() nos subdiretórios e, em seguida, retornar ao diretório original com fchdir(fd) . No entanto, nenhum shell que eu conheço tem algum suporte para isso.

Você pode fazer isso em perl :

#! /usr/bin/perl

opendir DIR, "." or die "opendir: $!";
while (readdir DIR) {
  if (chdir($_)) {
    do-something-there;
    chdir DIR || die "fchdir: $!";
  }
}
closedir DIR
    
por 17.03.2016 / 18:18
0

Com loops for

Para iterar sobre os subdiretórios de um diretório, você pode usar um loop for .

for dir in */; do
  echo "Doing something in $dir"
done

O curinga */ expande para subdiretórios no diretório atual e para links simbólicos para diretórios. Diretórios cujo nome começa com . (ou seja, diretórios que são arquivos de ponto) são omitidos. Se você quiser pular links simbólicos, pode fazê-lo explicitamente.

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  echo "Doing something in $dir"
done

Para executar um comando em todos os arquivos no diretório, use o curinga * . Mais uma vez, isso exclui os arquivos de ponto. Se o diretório estiver vazio, o curinga não será expandido.

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  if (set -- "$dir"*; [ "$#" -ne 0 ]); then
    somecommand -- "$dir"*
  fi
done

Se você precisar chamar o comando separadamente para cada arquivo, use um loop aninhado. No snippet a seguir, eu uso [ -e "$file" ] || [ -L "$file" ] para testar se o arquivo é um arquivo existente (excluindo links simbólicos quebrados) ou um link simbólico (quebrado ou não); essa condição só falha se "$file" for um padrão de caractere curinga não expandido devido ao diretório estar vazio.

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  for file in "$dir"*; do
    if [ -e "$file" ] || [ -L "$file" ]; then
      somecommand -- "$file"
    fi
  done
done

Se o comando precisar ter o subdiretório como seu diretório atual, existem duas maneiras de fazê-lo. Você pode chamar cd antes e depois.

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  if cd -- "$dir"; then
    for file in *; do
      if [ -e "$file" ] || [ -L "$file" ]; then
        somecommand -- "$file"
      fi
    done
    cd ..
  fi
done

Isso tem a desvantagem de poder falhar em casos de borda, como o subdiretório que está sendo movido durante a execução do comando ou o processo não ter permissão para ser alterado no diretório-pai. Para evitar esses casos de borda, uma alternativa para cd .. é executar o comando em um subshell. Um subshell duplica o estado do shell original, incluindo seu diretório atual, e o que acontece no subshell não afeta o processo original do shell.

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  ( cd -- "$dir" &&
    for file in *; do
      if [ -e "$file" ] || [ -L "$file" ]; then
        somecommand -- "$file"
      fi
    done
  )
  fi
done

Com achado

Como você pode ver, lidar com casos não nominais não é tão fácil. Alguns destes são casos de borda que você não pode se preocupar, mas um diretório vazio é algo que você deve ser capaz de lidar. Existem maneiras mais fáceis de lidar com esses casos se você usar ksh, bash ou zsh; acima eu mostrei o código que funciona em sh simples (e que não lida com arquivos de ponto).

Outra maneira de enumerar arquivos e diretórios é o comando find . Ele é projetado para percorrer árvores de diretórios de forma recursiva, mas você também pode usá-lo mesmo se não houver subdiretórios. Com o GNU ou FreeBSD find, ou seja, no Linux não integrado, Cygwin, FreeBSD ou OSX, existe uma maneira fácil de executar um comando em todos os arquivos em uma árvore de diretório:

find . ! -type d -execdir somecommand {} \;

Isso executa o comando em todos os arquivos que não são diretórios. Para executá-lo apenas em arquivos regulares (excluindo links simbólicos e outros tipos especiais de arquivos):

find . -type f -execdir somecommand {} \;

Se você deseja excluir os arquivos que estão diretamente no diretório de nível superior:

find . -mindepth 2 -type f -execdir somecommand {} \;

Se houver subdiretórios e você não quiser recorrer a eles:

find . -mindepth 2 -maxdepth 3 -type f -execdir somecommand {} \;
    
por 18.03.2016 / 01:56