Foreach loop para saída ls

0

Estou tentando processar alguns arquivos no diretório usando o loop foreach para a saída ls:

IFS=$'\n'
for i in $(ls -1Atu); do
    echo "$i"
done

Na primeira vez eu pensei que funciona, mas quando eu criei o arquivo com o nome como * ou filename* , shell adicionar iterações adicionais, porque * interpretado como wild char.
Mas, se eu escapar da saída ls como for i in "$(ls -1Atu)"; , terei uma única iteração e $ i conterá a saída de ls inteiramente.
Como podemos resolver este problema? Eu deveria notar que quaisquer variantes com ls -1Atu preferível, como eu preciso, o que os arquivos resultantes foram classificados por atime. Obrigado

    
por iRomul 23.04.2015 / 02:26

4 respostas

2

Leitura em segundo plano: Por que meu script de shell sufoca em espaços em branco ou outros caracteres especiais? , Por que você não deve analisar a saída de ls

A definição de IFS para uma nova linha significa que apenas novas linhas, e não espaços e tabulações, serão tratadas como separadores durante a expansão da substituição de comando. Seu método não suportará nomes de arquivos que contenham novas linhas; essa é uma limitação fundamental de ls ¹.

Além disso, e isso é o que você correu, definindo IFS não tem efeito sobre a outra coisa que acontece na expansão de uma substituição de comando, que é globbing (correspondência de caractere curinga). Você pode desativar globbing e seu script funcionará, desde que os nomes dos arquivos não contenham novas linhas.

IFS='
'
set -- *"$IFS"*
if [ -e "$1" ]; then
  echo >&2 "There are file names with newlines. I cannot cope with this."
  exit 2
fi
set -f
for i in $(ls -1Atu); do
    printf '%s\n' "$i"
done
set +f
unset IFS

A maneira fácil e confiável de enumerar arquivos por data é usar algo diferente de um shell estilo Bourne puro, como zsh que possui Qualificadores glob para modificar a maneira como as correspondências curinga são classificadas.

#!/bin/zsh
files_by_access_time=(*(Doa))

¹ Você pode contornar isso com algumas implementações de ls , mas não se você precisar de portabilidade.

    
por 24.04.2015 / 06:49
2

Por várias razões relacionadas a problemas de espaço em branco, etc., não é aconselhável analisar a saída de ls . Uma alternativa que usa versões GNU de find , sort , sed :

find . -mindepth 1 -maxdepth 1 -printf "%A@ %f
find . -mindepth 1 -maxdepth 1 -printf "%A@ %f
find . -mindepth 1 -maxdepth 1 -printf "%A@ %f
find . -mindepth 1 -maxdepth 1 -printf "%A@ %f%pre%" | 
  sort -rnz | 
  sed -z 's/^[0-9.]\+ //' |
  while IFS= read -r -d '' filename
  do 
    # do stuff with filenames
    printf "%s\n" "$filename"
  done
" | sort -rnz | sed -z 's/^[0-9.]\+ //'
" | sort -rnz | sed -z 's/^[0-9.]\+ //' | while IFS= read -r -d '' filename do # do stuff with filenames printf "%s\n" "$filename" done
" | sort -rnz | sed -z 's/^[0-9.]\+ //'
  • O find é, claro, muito mais flexível do que ls quando se trata de listar e filtrar arquivos, mas ele não faz a classificação sozinho.
  • Aqui, imprimo o tempo de modificação em segundos ( %A@ ), seguido pelo nome do arquivo e um caractere ASCII NUL, o único caractere seguro para delimitar nomes de arquivos.
  • sort -rnz , em seguida, recebe a entrada delimitada por NUL ( -z ) e classifica numericamente ( -n ), em ordem decrescente (pois -t de ls fornece a mais nova primeiro).
  • Então usei sed para remover o tempo inicial, novamente lidando com linhas delimitadas por NUL ( -z ).

A saída resultante é um fluxo de linhas delimitadas por NUL. No bash (não no sh simples), você pode lê-los em variáveis por:

%pre%

Isto é, claro, um pouco mais complicado, mas consideravelmente mais seguro.

    
por 23.04.2015 / 03:39
1
  1. Você realmente quer dizer adicionar * no nome do arquivo?
  2. Ou você quer dizer que a saída de ls dá nome de arquivo terminando em * se ele tiver permissão de execução?

Se apenas o problema de saída de ls , você poderia simplesmente resolver por:

substitua ls para \ls , isso é usar a versão sem alias de ls , que não gera *

    
por 23.04.2015 / 05:46
1

Você pode usar algo assim:

ls -1Atu | while IFS= read -r entry; do
    echo "$entry"
done

Com este exemplo, a saída é gerada uma vez e a seção while read entry faz com que a saída de ls seja analisada linha por linha, o que resolve o problema com o exemplo for em que tudo estava sendo colocado em $i em uma única rodada.

    
por 23.04.2015 / 02:33