Como fazer isso corretamente
Primeiro, cite sempre 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 de por que você o escolheu ) por uma questão de consistência.
Agora, vamos atribuir o caminho de pullingATerdon
para uma variável e, em seguida, 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, como esperado. Mas, se agora citamos 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á perfeitamente. OK, assim será vim
e qualquer outra coisa. Sua escolha de editor, apesar de infeliz, não é relevante.
Por que o seu falhou
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|<':
Que nos mostra que ls
foi executado com três argumentos distintos: '/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 seqüências de caracteres não citadas. Nesse caso, o problema é a divisão de palavras, uma vez que o shell viu os espaços no caminho e leu cada sequência separada por espaços como um argumento separado.
O mkdir
exemplo é um pouco diferente, mas é porque você está nos mostrando a mensagem de erro da segunda chamada do comando. Eu acho que você tentou uma vez e depois executou uma segunda vez para obter o resultado da sua pergunta. A primeira vez que você o executou, seria 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, por causa da divisão de palavras. Primeiro, ele 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 [`-_-"]'
Em seguida, também com sucesso, criou um diretório chamado +'|'|_e|\|\|0rth
no 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, em seguida, tentou criar o diretório [`-_-"]/pullingATerdon
. Isso falhou porque mkdir
, por padrão, não cria subdiretórios (pode, se você o executar -p
):
$ mkdir baz/bar
mkdir: cannot create directory ‘baz/bar’: No such file or directory
Como sua string não citada continha a /
, mkdir
considerou que o caminho de dois diretórios tentou encontrar o principal e falhou.
Por isso falhou, mas o que aconteceu é mais complicado. A seqüência de caracteres que você usou na verdade é um glob shell, especificamente uma gama glob , que corresponde a todos os arquivos no diretório atual, cujo nome é um dos 5 caracteres `
, -
, _
ou "
. Como você não possui esses arquivos no diretório atual, o glob não corresponde a nada e, como é o comportamento padrão no bash, retorna automaticamente:
$ 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ê fornecer uma glob que corresponda a algo:
$ echo [p]* ## any filename starting with a p
pullingATerdon
$ echo "[p]*" ## the string "[p]*"
[p]*
O não citado [p*]
é expandido para a lista de nomes de arquivos correspondentes (apenas um, neste caso) e é para isso que é passado echo
. Outra razão pela qual você deve citar todas as coisas.
Finalmente, o erro real que você mostra é da segunda vez em que você executou o comando e falha na primeira etapa, ao tentar criar /home/terdon/foo/\e[92mM@r|<
, porque a chamada anterior já havia criado esse diretório.
De maneira mais geral, sempre que você estiver trabalhando com nomes de arquivos arbitrários, use sempre shell globs. Coisas assim:
for file in *; do command "$file"; done
Isso funcionará para qualquer nome de arquivo. Não importa o que aconteça. No nosso exemplo acima, você poderia ter feito:
emacs /home/terdon/*92mM*/pullingATerdon
Qualquer glob que identifique o arquivo de destino exclusivamente fará isso. Dessa forma, você não precisa se preocupar com os caracteres especiais e pode deixar que o shell lide com eles.
Algumas referências úteis:
Como posso encontrar e lidar com segurança com nomes de arquivos contendo novas linhas, espaços ou ambos? : Uma das perguntas frequentes no excelente Wiki de Gray Cat.
Implicações de segurança de se esquecer de citar uma variável nos shells bash / POSIX : o mesmo post que referenciei 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 engasga com espaços em branco ou outros caracteres especiais? : tudo o que você sempre quis saber sobre como lidar com nomes de arquivos arbitrários no shell.
Quando é necessário aspas duplas? : Mais sobre aspas e variáveis e, especificamente, os poucos casos em que você não precisa citá-las
vim "$bacon"