Como fazer isso corretamente
Primeiro de tudo, sempre cite suas variáveis . O que você está tentando fazer funciona bem se você citá-lo corretamente:
$ pwd
/home/terdon/foo/\e[92mM@r|< +'|'|_e|\|\|0rth ['-_-"]
$ ls
pullingATerdon
Eu mantive o nome de arquivo estranho que você escolheu ( embora eu não tenha idéia do porque você o escolheu a>) por uma questão de consistência.
Agora, vamos atribuir o caminho de pullingATerdon
a uma variável e tentar abrir o arquivo:
$ bacon="$(realpath pullingATerdon)"
$ echo "$bacon"
/home/terdon/foo/\e[92mM@r|< +'|'|_e|\|\|0rth ['-_-"]/pullingATerdon
$ ls $bacon
ls: cannot access '+'\''|'\''|_e|\|\|0rth': No such file or directory
ls: cannot access '['-_-"]/pullingATerdon': No such file or directory
'/home/terdon/foo/\e[92mM@r|<':
Isso falha, conforme esperado. Mas, se agora citarmos corretamente:
$ ls -l "$bacon"
-rw-r--r-- 1 terdon terdon 0 Mar 14 23:15 '/home/terdon/foo/\e[92mM@r|< +'\''|'\''|_e|\|\|0rth ['-_-"]/pullingATerdon'
Funciona como esperado. E sim, você também pode abrir o caminho em um editor (adequado): emacs "$bacon"
funcionará bem. OK, então vim
e mais alguma coisa. Sua escolha de editor, embora infeliz, não é relevante.
Por que sua falha
Uma maneira rápida de rastrear o que realmente aconteceu no seu caso é usar set -x
(desligue-o novamente com set +x
), o que faz com que o shell imprima cada comando que será executado antes de executá-lo. ative as mensagens de depuração do shell com set -x
:
$ set -x
$ /bin/ls $bacon
+ ls '/home/terdon/foo/\e[92mM@r|<' '+'\''|'\''|_e|\|\|0rth' '['-_-"]/pullingATerdon'
ls: cannot access '+'\''|'\''|_e|\|\|0rth': No such file or directory
ls: cannot access '['-_-"]/pullingATerdon': No such file or directory
'/home/terdon/foo/\e[92mM@r|<':
Isso mostra que ls
foi executado com três argumentos separados: '/home/terdon/foo/\e[92mM@r|<'
, '+'\''|'\''|_e|\|\|0rth'
e '['-_-"]/pullingATerdon'
. Isso acontece porque o shell executa divisão de palavras e expansão glob em cadeias sem aspas. Nesse caso, o problema é a divisão de palavras, uma vez que o shell viu os espaços no caminho e leu cada string separada por espaços como um argumento separado.
O exemplo mkdir
é um pouco diferente, mas isso é porque você está nos mostrando a mensagem de erro da chamada segundo do comando. Eu acho que você tentou uma vez, e depois correu uma segunda vez para obter a saída para sua pergunta. A primeira vez que você o executou, teria sido assim:
$ mkdir $(realpath pullingATerdon)
++ realpath pullingATerdon
+ mkdir '/home/terdon/foo/\e[92mM@r|<' '+'\''|'\''|_e|\|\|0rth' '['-_-"]/pullingATerdon'
mkdir: cannot create directory ‘['-_-"]/pullingATerdon’: No such file or directory
Novamente, isso tentará criar três diretórios, não um, devido à divisão de palavras. Primeiro, criou (com sucesso) o diretório /home/terdon/foo/\e[92mM@r|<
:
$ ls -l /home/terdon/foo/
total 8
drwxr-xr-x 2 terdon terdon 4096 Mar 15 00:20 '\e[92mM@r|<'
drwxr-xr-x 3 terdon terdon 4096 Mar 15 00:20 '\e[92mM@r|< +'\''|'\''|_e|\|\|0rth ['-_-"]'
Então, também, com sucesso, criou um diretório chamado +'|'|_e|\|\|0rth
em seu diretório atual:
$ ls -l
total 4
drwxr-xr-x 2 terdon terdon 4096 Mar 15 00:37 '+'\''|'\''|_e|\|\|0rth'
-rw-r--r-- 1 terdon terdon 0 Mar 15 00:36 pullingATerdon
E então, tentou criar o diretório ['-_-"]/pullingATerdon
. Isso falhou porque mkdir
, por padrão, não cria subdiretórios (pode, se você executá-lo com -p
):
$ mkdir baz/bar
mkdir: cannot create directory ‘baz/bar’: No such file or directory
Como sua string não citada continha um /
, mkdir
considerou que um caminho de dois diretórios tentou localizar o primeiro e falhou.
É por isso que ele falhou, mas o que aconteceu é mais complicado. A string que você usou é na verdade um shell glob, especificamente um intervalo glob , que combina todos os arquivos no diretório atual cujo nome é um dos 5 caracteres '
, -
, _
ou "
. Como você não tem esses arquivos em seu diretório atual, o glob não corresponde a nada e, como é o comportamento padrão no bash, ele retorna:
$ echo "[\'-_-\"]/pullingATerdon" ## some escaping is needed here
+ echo '['-_-"]/pullingATerdon' ## but it echoes the right thing
['-_-"]/pullingATerdon ## and matches nothing, so returns itself.
Para esclarecer, eis o que acontece se você der um globo que corresponda a algo:
$ echo [p]* ## any filename starting with a p
pullingATerdon
$ echo "[p]*" ## the string "[p]*"
[p]*
O [p*]
sem aspas é expandido para a lista de nomes de arquivos correspondentes (apenas um, neste caso) e é o que é passado para echo
. Ainda outra razão pela qual você deve citar todas as coisas.
Por fim, o erro real mostrado é da segunda vez que você executou o comando e falha na primeira etapa, ao tentar criar /home/terdon/foo/\e[92mM@r|<
, porque a invocação anterior já havia criado esse diretório.
Em geral, sempre que você estiver trabalhando com nomes de arquivos arbitrários, sempre use shell globs. Coisas assim:
for file in *; do command "$file"; done
Isso funcionará para qualquer nome de arquivo. Não importa o que isso aconteça. Em nosso exemplo acima, você poderia ter feito:
emacs /home/terdon/*92mM*/pullingATerdon
Qualquer glob que identifique o arquivo de destino será o único. Dessa forma, você não precisa se preocupar com os caracteres especiais e pode simplesmente deixar o shell lidar com eles.
Algumas referências úteis:
-
Como posso encontrar e lidar com segurança com nomes de arquivos que contenham novas linhas, espaços ou ambos? : Uma das perguntas frequentes sobre o excelente Wiki do gato cinzento.
-
Implicações de segurança de esquecer de citar uma variável em shells bash / POSIX : o mesmo post que eu referenciado no início desta resposta. Uma explicação excelente e muito detalhada de todas as coisas que podem dar errado se você não citar corretamente as variáveis do shell.
-
Por que meu script de shell sufoca em espaços em branco ou outros caracteres especiais? : tudo que você sempre quis para saber como lidar com nomes de arquivos arbitrários no shell.
-
Quando é necessário cotar duas vezes?: Mais sobre cotações e variáveis e, especificamente, os poucos casos em que você não precisa citá-los