É possível manipular corretamente todos os nomes de arquivos possíveis? [fechadas]

0

No linux, apenas dois caracteres são proibidos em nomes de arquivos "barra" e "caractere nulo". Assim, todos os caracteres com significado especial em cada linguagem de script devem ser ignorados, MAS todas as seqüências de escape também são permitidas em nomes de arquivos! Pior ainda, ou seja, bash alguns métodos de escape escapa apenas alguns caracteres, por isso, para escapar grande quantidade de caracteres diferentes, você deve usar um par de diferentes métodos de fuga juntos, mas eles interferem uns com os outros! Ainda pior que alguns comandos usam alguns caracteres para seus propósitos, e outros comandos usam outros, então para cada simples operação nos arquivos você deve escapar o nome do arquivo de forma diferente! Ainda pior que apenas o caractere nulo poderia ser usado para separar nomes de arquivos com segurança, mas a maioria dos comandos não pode trabalhar com isso. Pior ainda, que no linux basicamente tudo é arquivo ... Então, isso não parece apenas incômodo, mas a questão da segurança e estabilidade, porque grande parte do linux é baseada em script tão falho!

Então mostre-me onde estou errado ... É possível manipular corretamente todos os nomes de arquivos possíveis?

Clarificação. Originalmente eu queria:

  1. lista arquivos e pastas sob determinado caminho

  2. lista de pesquisa para encontrar as que correspondem a determinados critérios (idade ou padrão ou tamanho do arquivo)

  3. mover arquivos e pastas correspondentes para categorias, por exemplo, filmes Por causa da complexidade dos testes, não era possível (ou prático) fazê-lo em um comando, então eu tive que passar o nome do arquivo entre os diferentes comandos. Bash globbing foi a primeira coisa a se desfazer por causa de espaços em nomes de arquivos. Globbing sempre divide o nome do arquivo com espaços para dois elementos da lista. Então eu tentei usar "find". Isso foi melhor, mas muito mais lento e difícil de usar.

Não consigo usar nenhum caractere especial para escapar do nome do arquivo, porque não sei qual caractere pode estar no nome do arquivo. Depois de alguns testes, descobri que é uma questão de tempo até que qualquer caractere ocorra.

Eu tentei o filtro definido como: %código% Logo percebi que desta forma eu não posso definir filtros para múltiplos usos, porque globbing chuta para longe. Então eu desabilitei o globbing e o histórico por audio_ext=(*.mp3 *.wav *.ogg *.mid *.mod *.stm *.s3m *.it *.wma *.669 *.ac3) . Sem globbing eu tive que fazer a expansão pela mão

set -fH

Em que while IFS= read -r -d $'params'; do list+=("$REPLY") done < <( find . -maxdepth 1 -mindepth 1 ${params[@]} -print0 2>/dev/null ) é uma matriz como "-iname" "*.mp3" "-o" "-iname" "*.wav" etc. Isso funcionou até o arquivo ter "(" no nome. Localizar retornou erro sobre uso incorreto.

Para dizer a verdade ... Eu usei script em lote para esta tarefa até recentemente por 15 anos. O tempo gasto em escrever era de uma ou duas tardes. Ele tinha inconvenientes e problemas com ! em nomes de arquivos, mas geralmente funcionava. Agora eu tenho tentando quase dois meses para escrevê-lo no bash. É feio, complicado, com muito bugs, e parece que nunca vai funcionar bem.

    
por harvald 12.04.2018 / 18:20

2 respostas

4

Nomes de arquivo podem usar qualquer caractere, exceto o caractere nul ( find ) e barra, que é um separador de caminho. Variáveis podem conter quaisquer dados (exceto caracteres nul na maioria dos shells). Se devidamente citados, os nomes de arquivos podem ser armazenados com segurança em variáveis e usados com utilitários.

Em relação aos seus pontos:

Para iterar sobre um conjunto de arquivos (arquivos ou diretórios regulares), você pode usar um loop de shell simples como

for name in ./*; do
    # some code that uses "$name"
done

Para iterar arquivos durante a seleção de arquivos específicos usando critérios específicos, N é a melhor opção. por exemplo, para selecionar todos os arquivos regulares no diretório atual (ou abaixo) que sejam mais antigos que N dias (tenha data de modificação pelo menos -size dias no passado):

find . -type f -mtime +N

Da mesma forma, -name é usado para selecionar arquivos com base no tamanho e *.mov para corresponder ao nome do arquivo em um padrão de globbing.

Por exemplo, para selecionar os arquivos regulares que possuem nomes de arquivos que correspondem a $HOME/Movies e que foram modificados na última semana:

find . -type f -name '*.mov' -mtime -7

Então, realmente faça algo com esses arquivos, como movê-los para o diretório {} :

find . -type f -name '*.mov' -mtime -7 -exec mv {} "$HOME/Movies" ';'

O mv será substituído pelo nome do caminho do arquivo na invocação de {} . Você não precisa citar o find (ele não mudará nada se você o fizer), pois find não invocará a divisão de palavras ou a expansão de nome de arquivo do shell no nome do caminho.

Uma melhoria adicional para isso seria detectar colisões de nomes de arquivos no diretório de destino. Para isso, usamos um script de ajuda curto, que terá vários nomes de arquivos em sua linha de comando:

destdir="$HOME/Movies"
for name do
    if [ -f "$destdir/${name##*/}" ]; then
        printf "%s already exists in %s, not overwriting it!\n" "${name##*/}" "$destdir" >&2
    else
        mv "$name" "$destdir"
    fi
done

ou, em formato de atalho:

destdir="$HOME/Movies"
for name do
    [ -f "$destdir/${name##*/}" ] && printf "skipping %s\n" "$name" >&2 && continue
    mv "$name" "$destdir"
done

Conectando isso ao nosso comando %code% :

find . -type f -name '*.mov' -mtime -7 -exec sh -c '
    destdir="$HOME/Movies"
    for name do
        [ -f "$destdir/${name##*/}" ] && printf "skipping %s\n" "$name" >&2 && continue
        mv "$name" "$destdir"
    done' sh {} +

Em nenhum lugar ao longo do caminho, permitimos que o shell faça a divisão de palavras ou a globalização de nomes de arquivo no nome do caminho ou no nome do arquivo que estamos processando no momento.

Para mais informações:

por 12.04.2018 / 22:03
8

Simples. Use globbing para selecionar os arquivos desejados e cite a variável que contém o nome do arquivo:

shopt -s nullglob
for file in ./*.txt; do
    do_something_with "$file"
done

Isso é tudo que existe para isso.

Mais detalhes:

Atualização: globbing não é responsável pelo efeito de divisão de palavras que você está vendo. Não citar a variável é.

Você pode obter informações sobre arquivos para suas condições com stat

read size mtime < <(stat -c "%s %Y" "$file")
[[ $size -gt 1000 ]] && echo "too big"
[[ $mtime -lt $(date -d yesterday +%s) ]] && echo "too old"

Atualização 2: criar um nome de arquivo com muitos caracteres especiais requer misturar vários mecanismos de citação, mas ainda é possível fazer qualquer coisa com esse arquivo.

$ filename='~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?''"'"$' \a\t\n\r\f'".txt"
#          ^^ single quoted part ^^^^^^^^^^^^^^^^   
#                             double quoted part ^^^
#                                ANSI-C quoted part ^^^^^^^^^^^^^^

$ echo "$filename"
~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?''   

.txt

$ printf "%q\n" "$filename"
$'~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?'\' \a\t\n\r\f.txt'

$ date > "$filename"

$ cat "$filename"
Thu Apr 12 15:14:29 EDT 2018

$ ls -lt
total 3836
-rw-rw-r-- 1 jackman jackman      29 Apr 12 15:14 ~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?'' ?????.txt
                ︙

$ ls -lt --show-control-chars
total 3836
-rw-rw-r-- 1 jackman jackman      29 Apr 12 15:14 ~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?''     

.txt
                ︙

Se a saída de ls for redirecionada para algo diferente de um terminal (por exemplo, um arquivo ou um canal), Ele usará o estilo --show-control-chars por padrão. Você pode ver isso executando ls -lt | cat . ls tem outras opções de exibição; por exemplo, --quoting-style=WORD .

    
por 12.04.2018 / 18:50