Como posso armazenar uma string literal em uma variável sem substituição ou interpretação?

3

Para nivelar uma estrutura de diretórios, posso fazer isso:

find . -type f -exec sh -c 'mv "{}" "./'basename "{}"'"'  \;

Eu quero armazenar o seguinte no meu perfil como $ FLATTEN

-exec sh -c 'mv "{}" "./'basename "{}"'"'  \;

para que mais tarde eu possa executar find . $FLATTEN

Estou tendo problemas para armazenar a variável porque ela é interpretada cedo demais. Eu quero que ele seja armazenado como uma string literal e interpretado apenas em uso no shell, não quando originado.

    
por haventchecked 17.09.2017 / 21:35

4 respostas

9

Se estiver usando o GNU mv , você deve fazer:

find . -type f -exec mv -t . {} +

Com outros mv s:

find . -type f -exec sh -c 'exec mv "$@" .' sh {} +

Você nunca deve incorporar {} no código sh . Essa é uma vulnerabilidade de injeção de comando, pois os nomes dos arquivos são interpretados como códigos de shell (tente com um arquivo chamado 'reboot' , por exemplo).

Bom ponto para citar a substituição de comando, mas como você usou o formulário arcaico ( '...' em oposição a $(...) ), você precisaria escapar das aspas duplas internas ou ele não funcionará em sh implementações baseadas no shell Bourne ou AT & T ksh (onde "'basename "foo bar"'" seria realmente tratado como "'basename " (com um ' incomparável que é aceito em essas conchas) concatenadas com foo e, em seguida, bar"'" ).

Além disso, quando você faz:

mv foo/bar bar

Se bar existisse e fosse um diretório, na verdade seria um mv foo/bar bar/bar . mv -t . foo/bar ou mv foo/bar . não têm esse problema.

Agora, para armazenar esses vários argumentos ( -exec , sh , -c , exec mv "$@" . , sh , {} , + ) em uma variável, você precisaria de uma variável de matriz . Shells que suportam matrizes são (t)csh , ksh , bash , zsh , rc , es , yash , fish .

E para poder usar essa variável como apenas $FLATTEN (em oposição a "${FLATTEN[@]}" em ksh / bash / yash ou $FLATTEN:q em (t)csh ), você precisaria de um shell com uma implementação de matriz sã : rc , es ou fish . Também zsh aqui, como acontece, nenhum desses argumentos está vazio.

Em rc / es / zsh :

FLATTEN=(-exec sh -c 'exec mv "$@" .' sh '{}' +)

Em fish :

set FLATTEN -exec sh -c 'exec mv "$@" .' sh '{}' +

Então você pode usar:

find . -type f $FLATTEN
    
por 17.09.2017 / 22:37
4

Seria melhor usar uma função de shell para isso, e para obter o -exec correto (não coloque {} em um subshell):

flatten () {
     ( cd "${1:-.}" && 
       find . -type f -exec sh -c 'for n; do test -e "${n##*/}" || mv "$n" "${n##*/}"; done' sh {} + )
}

Isso também não precisa chamar o utilitário externo basename e não tentará substituir as coisas já existentes.

Você usaria isso apenas digitando flatten e ele agirá no diretório atual. Dar a ele um nome de diretório fará com que ele aja (copiando todos os arquivos sob esse diretório para o topo desse diretório).

    
por 17.09.2017 / 21:44
3

Uma função é provavelmente o melhor caminho. Caso contrário, você precisa de uma matriz ou eval :

find_array=(-exec sh -c 'mv "{}" "./'basename "{}"'"'  \;)
find . "${find_array[@]}"

ou

FLATTEN="-exec sh -c 'mv \"{}\" \"./'basename \"{}\"'\"'  \;"
eval find . $FLATTEN
    
por 17.09.2017 / 21:40
3

Que tal uma função?

flatten(){
  find "$@" -type f -exec sh -c 'mv -- "$0" "${0##*/}"' {} \;
}

Uso:

> flatten .

Se você usar zsh , há a opção -g de alias para isso. Ele permite que você defina um alias que seja inserido globalmente, não apenas no nome do comando.

    
por 17.09.2017 / 21:37