Use o awk para enviar uma lista de links simbólicos para os arquivos em um diretório com espaços no nome?

3

Eu tenho uma lista de nomes de arquivos que eu quero criar links para (chamam filenames.txt ). Ao usar

cat filenames.txt | awk 'system("ln -s "$0" ~/directory\ with\ spaces/subdirectory/")'

parece que ele fica obstruído quando encontra algo (um nome de arquivo ou o diretório de destino) com um espaço.

Como faço para criar esses links sem primeiro renomear tudo para garantir que não haja espaços?

    
por cjm 15.01.2017 / 08:02

3 respostas

2

OBSERVAÇÃO: Todos os exemplos apresentados aqui devem ser executados no diretório em que os arquivos e a lista de nomes de arquivos estão localizados. Por exemplo, se estiverem na pasta /mnt/Hard\ Drive/some\ files/ , assegure-se de que filenames.txt esteja armazenado lá também. Ao executar os comandos do shell, por favor, cd /mnt/Hard\ Drive/some\ files/ e execute os exemplos.

AWK

A chamada de sistema do AWK pode ser usada com itens que contenham espaços, mas é um pouco complicado. Seu código original tinha alguns erros de sintaxe e cat inútil. Independentemente desse fato, o caminho apropriado seria o comando ser construído com sprintf() na variável primeiro e passar essa variável para system() . Assim:

$ ls dir\ with\ spaces/                                                                                                  
$ awk '{cmd=sprintf("fpath=$(realpath -e \"%s\" );%s %s \"$fpath\" \"%s\"",$0,"ln","-s","dir with spaces/");system(cmd) }' filenames.txt                   
$ ls dir\ with\ spaces/                                                                                                  
ascii.txt@  disksinfo.txt@  input.txt@  process info.txt@

BASH (ou qualquer shell parecido com o Bourne)

O caminho da menor resistência seria via script de shell pequeno dado abaixo:

#!/bin/bash
# uncomment set -x for debugging
#set -x 

# Preferably, the directory should be full path
dir="dir with spaces/"

while IFS= read -r line
do
    if [ "x$line" != "x"  ] && [ -e "$line" ];
    then
        fpath=$( realpath -e "$line" )
        ln -s "$fpath" "$dir"
    fi
done < filenames.txt

Script em ação:

$ ls -l dir\ with\ spaces/                                                                                        
total 0
$ cat filenames.txt                                                                                               
input.txt
ascii.txt
disksinfo.txt
process info.txt
$ ./makelinks.sh                                                                                                  
$ ls -l dir\ with\ spaces/                                                                                        
total 0
lrwxrwxrwx 1 xieerqi xieerqi  9 1月  15 00:47 ascii.txt -> ascii.txt
lrwxrwxrwx 1 xieerqi xieerqi 13 1月  15 00:47 disksinfo.txt -> disksinfo.txt
lrwxrwxrwx 1 xieerqi xieerqi  9 1月  15 00:47 input.txt -> input.txt
lrwxrwxrwx 1 xieerqi xieerqi 16 1月  15 00:47 process info.txt -> process info.txt

A maneira como funciona é simples: while IFS= read -r line; do . . . done < filenames.txt lê os arquivos de nomes.txt linha por linha, cada vez que a linha entra em line variable. Verificamos se essa linha não está vazia e se o arquivo existe (esse script deve ser executado no mesmo diretório onde os arquivos originais e a lista de arquivos estão localizados). Se ambas as condições forem verdadeiras, fazemos links.

Note que você deve garantir que o arquivo termine com uma nova linha - se houver uma última linha que não termine com caractere de nova linha (o que acontece), essa linha final será ignorada, portanto, não haverá nenhum link feito para esse arquivo.

Apesar de não ser ideal e não sem peculiaridades, esta é uma solução funcional para casos em que o system() do awk não está disponível (o que provavelmente é raro hoje em dia).

xargs

Uma abordagem de shell um pouco mais simples seria via xargs e bash -c '' sh , onde lemos filenames.txt, novamente, linha por linha com -L1 flag, e usamos cada linha como argumentos para bash. Usando $@ , pegamos toda a matriz de argumentos da linha de comando ($ 1, $ 2, $ 3 ... etc.) e transformamos na variável string, que é então passada para ln . O sh no final é definir a variável $0 . É discutível se é necessário fazer, mas esse não é o tópico desta questão, por isso vamos ignorá-lo:)

$ xargs -I {} -L1 bash -c 'file="$@";fpath=$(realpath -e "$file"); ln -s "$fpath"  ./dir\ with\ spaces/"$file" ' sh < filenames.txt                      
$ ls dir\ with\ spaces/                                                                                                  
ascii.txt@  disksinfo.txt@  input.txt@  process info.txt@

Python

$ ls dir\ with\ spaces/ 
$ python -c "import os,sys;fl=[f.strip() for f in sys.stdin];map(lambda x: os.symlink(os.path.realpath(x),'./dir with spaces/' + x),fl)" < filenames.txt
$ ls dir\ with\ spaces/                                                                                                  
ascii.txt@  disksinfo.txt@  input.txt@  process info.txt@

A maneira como isso funciona é simples - redirecionamos filenames.txt para stdin de python, lemos cada linha na lista fl e depois executamos os.symlink() para cada item na lista. Comprimento de uma linha, mas funciona.

Perl

Uma versão mais curta é obtida via Perl:

$ perl -lane 'use Cwd "realpath";symlink(realpath($_),"./dir with spaces/" . $_)' < filenames.txt                                                     
$ ls dir\ with\ spaces/                                                                                                  
ascii.txt@  disksinfo.txt@  input.txt@  process info.txt@
    
por 15.01.2017 / 08:49
2

Para esse tipo de tarefa, não use uma linguagem de script que chame um comando externo com system() ou uma função semelhante. Ele apresenta os mesmos perigos e armadilhas como o eval da Bourne Shell.

xargs permite que você faça isso de maneira segura e eficiente:

xargs -d '\n' ln -s -t ~/directory\ with\ spaces/subdirectory/ < filenames.txt

O que ele faz é construir um comando adicionando o conteúdo de filenames.txt no final do comando que você deu como parâmetro. Então executa isto. Com o exemplo acima, o comando que ele criaria teria a seguinte aparência:

ln -s -t directory file1 file2 ... fileN

O que é interessante é que você não precisa citar nada em filenames.txt, e você não precisa usar aspas aninhadas para o diretório.

    
por 15.01.2017 / 13:16
0

No literal da string, use uma barra invertida dupla para obter uma única barra invertida na string que é passada para o shell, o que faz com que o shell interprete o próximo caractere literalmente:

awk 'system("… ~/directory\ with\ spaces/subdirectory/")'

Para citar a entrada para uso em um fragmento de shell, um método é adicionar uma barra invertida antes de cada caractere. O awk portátil não facilita isso (o GNU awk tem extensões que o fazem). Como alternativa, coloque uma única citação em torno de cada elemento e substitua todas as aspas simples no elemento por '\'' . O Awk facilita essa solução: use apenas gsub . Se o seu fragmento de awk estiver em um shell script, você pode usar um escape de backslash-octal para colocar uma aspa simples em um literal de string do awk.

<filenames.txt awk '{
    gsub(/7/, /7\77/, $0);
    system("ln -s 7" $0 "7 ~/directory\ with\ spaces/subdirectory/");
}'
    
por 16.01.2017 / 00:36