expande curingas “dentro de um comando” em vez de no shell

1

(Este é um exemplo de trabalho mais simples de algo em um contexto muito mais confuso.)

Eu tenho um diretório d com arquivos 1.txt 2.txt 3.txt 4.wav . Meu diretório atual está em outro lugar.

ls d relatórios 1.txt 2.txt 3.txt 4.wav , como deveria.

Mas eu quero apenas 1.txt 2.txt 3.txt , então tento ls d/*.txt .

Mas isso preends um d/ para cada nome de arquivo: d/1.txt d/2.txt d/3.txt , porque o shell expandiu o * antes de ls .

Para obter meu 1.txt 2.txt 3.txt desejado, eu poderia ls d | grep txt ou ls d/*.txt | xargs basename -a ou até mesmo gerar um novo shell: (cd d; ls *.txt) . Existe uma maneira melhor, menos desajeitada, menos vulnerável a pegadinhas e casos de canto, para filtrar a saída de ls ?

    
por Camille Goudeseune 04.05.2016 / 21:45

2 respostas

2

Eu acredito que a maneira mais simples de fazer o que você pede é:

$ ( cd d; ls *.txt )
1.txt  2.txt  3.txt

que está acontecendo dentro de um sub-shell ( ... ) , então a alteração do diretório não é permanente, é válida apenas para a execução dos dois comandos.

Uma versão mais robusta é:

$ ( cd d && ls -d -- *.txt )
1.txt  2.txt  3.txt

Em que ls não é executado, a menos que a alteração do diretório tenha sido bem-sucedida, e ls lista diretórios em vez de seu conteúdo e não aceita arquivos cujo nome comece com um traço como na opção.

Se você não se importa em alterar os parâmetros posicionais ( $1 , $2 , etc.):
Isso também é relativamente simples:

$ set d/*.txt
$ for f do printf '%s\n' "${f#d/}"; done
1.txt
2.txt
3.txt

Isso funcionará em shells POSIX:

$ dash -c 'set d/*.txt; for f do printf "%s\n" "${f#d/}"; done'
1.txt
2.txt
3.txt

Você pode usar o -printf :

do GNU find
$ find d -maxdepth 1 -iname "*.txt" -printf "%f\n" | sort
1.txt
2.txt
3.txt

Mas isso está ficando complexo o suficiente. Desculpe, não há mais soluções "simples" AFAIK.

    
por 04.05.2016 / 23:26
3

com zsh :

printf '%s\n' d/*.txt(:t)

:t como para os modificadores de histórico do csh, mas aqui em um qualificador de glob, você obtém o tail do nome do arquivo.

Além disso:

files=(d/*.txt)
printf '%s\n' $files:t

Em outros shells parecidos com Bourne, você sempre pode fazer:

(cd d && printf '%s\n' *.txt)

Observe que ele não cria um novo shell, cria um ambiente de subshell. Na maioria das implementações de shell, esse ambiente subshell é implementado ao bifurcar um processo filho, mas um novo shell não é executado nele (não é um novo shell, é o mesmo shell bifurcado em um novo processo). Observe também que, se o último comando na subshell for um comando externo, a maioria dos shells (não bash embora) não formarão um processo extra, portanto, o número total de processos executados será o mesmo que sem o ambiente de sub-shell.

ksh93 não bifurca para subshells. Ele faz isso desfazendo as modificações feitas dentro da subcamada ao sair dela. Então, lá (onde printf também é interno), (cd d && printf '%s\n' *.txt) não bifurca processos extras.

Observe também que ls lista os arquivos e conteúdo dos diretórios que estão sendo passados como argumentos. Aqui, você não precisa de ls se for apenas para imprimir os nomes dados pelo shell, mas se você insistir em usar ls , você deve passar a opção -d para não listar o conteúdo dos diretórios :

(cd d && ls -d -- *.txt)
    
por 04.05.2016 / 22:02