Como posso criar meus próprios "comandos de shell" (por exemplo, mkdir / cd combo)?

41

Não sei dizer quantas vezes desejei um comando que crie um diretório e mude para esse diretório. Basicamente, eu gostaria do equivalente ao seguinte:

mkdir -p /arbitrarily/long/path; cd /arbitrarily/long/path

mas apenas tendo que digitar /arbitrarily/long/pathuma vez, algo como:

mk-cd /arbitrarily/long/path

Tentei criar um script para fazer isso, mas ele apenas altera o diretório dentro do script. Eu gostaria que o diretório no shell também tivesse mudado.

#!/bin/bash
mkdir $1
cd $1
export PWD=$PWD

Como eu posso fazer isso funcionar?

Christopher Bottoms
fonte
12
Com cd, você escolheu um caso especial desde o início. : D
Daniel B
2
Não é exatamente o que eu estava procurando, mas cdinformações relacionadas a informações muito legais (retorne ao diretório anterior usando cd -, use pushde popdpara manter uma "pilha" de diretórios): superuser.com/questions/324512/…
Christopher Bottoms
8
Você pode digitar mkdir -p /very/long/path, usar cdespaço e pressionar Alt + .para repetir o último argumento, ou seja, o nome do diretório.
choroba
2
Não é uma resposta, apenas um comentário semelhante ao @ choroba: Você também pode usar mkdir -p /very/long/path; cd !#:2. A string !#:2será expandida para o argumento nr. 2 (ou seja, o terceiro argumento /very/long/path, conforme a contagem começa com zero).
Ingo Blechschmidt
3
@IngoBlechschmidt, ainda mais fácil, já que é o último argumento do último comando, você pode apenas usar !$. Eu uso esse truque em particular o tempo todo, embora haja muito mais a fazer com a expansão do histórico .
Curinga

Respostas:

92

A maneira mais simples é usar uma função shell:

mkcd() {
    mkdir -p -- "$1" && cd -- "$1"
}

Coloque-o no seu .bashrcarquivo para torná-lo disponível, assim como outro comando do shell.

A razão pela qual ele não funciona como um script externo é cdaltera o diretório atual do script em execução, mas não afeta o chamado. Isso é por design! Cada processo tem seu próprio diretório de trabalho que é herdado por seus filhos, mas o oposto não é possível.

A menos que parte de um pipeline seja executado em segundo plano ou explicitamente em um subshell, uma função shell não é executada em um processo separado, mas no mesmo, como se o comando tivesse sido originado. O shell do diretório atual pode ser alterado por uma função.

O &&usado aqui para separar os dois comandos utilizados significa que, se o primeiro comando for bem-sucedido ( mkdir), execute o segundo ( cd). Consequentemente, se mkdirnão conseguir criar o diretório solicitado, não faz sentido tentar entrar nele. Uma mensagem de erro é impressa mkdire é isso.

A -popção usada com mkdirexiste para informar este utilitário para criar qualquer diretório ausente que faça parte do caminho completo do nome do diretório passado como argumento. Um efeito colateral é que, se você pedir para criar um diretório já existente, a mkcdfunção não falhará e você terminará nesse diretório. Isso pode ser considerado um problema ou um recurso. No primeiro caso, a função pode ser modificada, por exemplo, da maneira que simplesmente avisa o usuário:

mkcd() {
    if [ -d "$1" ]; then
        printf "mkcd: warning, \"%s\" already exists\n" "$1"
    else
        mkdir -p "$1" 
    fi && cd "$1"
}

Sem a -popção, o comportamento da função inicial teria sido muito diferente.

Se o diretório que contém o diretório a ser criado ainda não existir, mkdira função também falhará.

Se o diretório a ser criado já existir, mkdirfalhará também e cdnão será chamado.

Por fim, observe que a configuração / exportação PWDé inútil, como o shell já faz internamente.

Editar: adicionei a --opção aos dois comandos da função para permitir um nome de diretório que comece com um traço.

jlliagre
fonte
5
Você também deve adicionar que esse comando não ficará disponível permanentemente, a menos que seja adicionado a ~/.bashrcou um dos outros scripts de inicialização.
AFH
Não existe maneira no bash de fazer o equivalente a tcsh's alias mk-cd 'mkdir -p \!:1; cd \!:1'sem uma função? Ou talvez eu devesse começar a pensar nas funções do bash mais como aliases estendidos?
Thomas Padron-McCarthy
@ ThomasPadron-McCarthy Este cshmecanismo de passagem de argumentos não é implementado com aliases de estilo bourne. As funções são realmente o caminho a percorrer, sendo muito mais flexíveis.
Jlliagre
@ ThomasPadron-McCarthy - Você pode olhar para esta resposta , que fornece um mecanismo bastante obscuro para acessar parâmetros passados ​​para um alias (na definição de bar). Outra opção seria usar history 1, como em alias mk-cd='args="$(history 1)" && args="${args#*mk-cd\ }" && mkdir -p "$args" && cd'- isso funciona bem para o exemplo do questionador, mas não é uma solução geral, pois qualquer outro comando na mesma linha (por exemplo, canalizar a saída) fará com que ela falhe.
AFH
3
@ ThomasPadron-McCarthy Você deve começar a pensar nos pseudônimos do tcsh mais como funções restritas.
Gilles 'SO- stop be evil'
32

Eu gostaria de adicionar uma solução alternativa.

Seu script vai funcionar se você invocá-lo com o . ou sourcecomando:

. mk-cd /arbitrarily/long/path

Se você fizer isso export, o script não será necessário. Você pode ignorar a digitação .usando um alias:

alias mk-cd='. mk-cd'

A razão pela qual isso funciona é que um script normalmente é executado em um sub shell, para que seu ambiente seja perdido na conclusão, mas o comando .(ou source) o força a executar no shell atual.

Essa técnica pode ser útil para seqüências de comandos que são muito complexas para uma função ou que estão em desenvolvimento e são frequentemente editadas: uma vez aliasinserida .bash_aliases, você pode editar o script à vontade sem reinicializar.

Observe o seguinte: -

Se você escrever um script que deve ser chamado com o comando ./ source, há duas maneiras de garantir isso:

  1. Por alguma razão, um script invocado com ./ sourcenão precisa ser executável; portanto, remova essa permissão e o script não será encontrado em "$ PATH" ou causará um erro de permissão se invocado com um caminho explícito.

  2. Como alternativa, o seguinte truque pode ser usado na cabeça do script:

bind |& read && { echo 'Must be run from "." or "source" command'; exit 1; }

Isso funciona porque em um sub-shell bindemite um aviso, embora não haja status de erro: portanto, readé usado para dar um erro em nenhuma entrada.

AFH
fonte
Obrigado pela informação em .. Já fiz . .bashrcinúmeras vezes, mas não sabia exatamente o que .faz. É semelhante a export?
cst1992
2
@ cst1992 - Não, os comandos são completamente diferentes: export varcopia a variável interna varpara o ambiente, onde é herdada e importada como variável em qualquer sub shell, mas não tem efeito em um shell pai. Normalmente, um sub-shell é criado para executar um script e todas as alterações que ele faz (incluindo variáveis ​​exportadas) são perdidas quando concluídas. Para que um script defina variáveis ​​em um shell, ele deve ser executado nesse shell, e é isso que o .comando faz: execute o script sem criar um sub shell. Curiosamente, os scripts executados por .não precisam ter permissão executável.
AFH
Obrigado pela informação. Eu insisto que você inclua essas informações em sua postagem. Isso é útil e, francamente, os comentários não têm garantia de que eles estarão lá amanhã.
cst1992
@ cst1992 - Abordei a pergunta original, mas não quero confundir minha resposta com questões acessórias. Se você estivesse procurando uma explicação dos comandos .e export, o título desta pergunta não levaria você a esperar uma resposta aqui, então deixarei minha resposta como está, mas obrigado pelo seu comentário.
AFH
+1 em geral, mas especialmente para o alias. Eu tenho alguns scripts que precisam ser adquiridos e sempre esqueço de executá-los dessa maneira. O alias cuida disso muito bem.
Joe
13

Não é um comando único, mas você não precisa digitar novamente todo o caminho usado !$(que é o último argumento do comando anterior no histórico do shell).

Exemplo:

mkdir -p /arbitrarily/long/path
cd !$
RiaD
fonte
1

Criando aliases . Maneira muito popular de criar seus próprios comandos.

Você pode criar um alias rapidamente:

alias myalias='mkdir -p /arbitrarily/long/path; cd /arbitrarily/long/path'

É isso aí. Se você deseja manter esse alias permanentemente (não apenas a sessão atual), coloque esse comando em um arquivo de pontos (normalmente ~ / .bash_aliases ou ~ / .bash_profile).

James
fonte
4
Como o nome do diretório longo é codificado permanentemente, esse alias não seria muito útil, a menos que este diretório seja destruído regularmente por algum outro processo.
Jlliagre # 9/16
1

Além do que já foi sugerido, eu gostaria de recomendar o caralho . É uma estrutura Python para otimizar seu processo de shell, corrigindo erros. Inspirado por esse tweet , tudo o que você precisa fazer é digitar seu alias configurado (por padrão fuck) e ele tentará corrigir o comando anterior. Uma de suas correções se comporta da seguinte maneira:

/etc $ cd somedir
bash: cd: No such file or directory
/etc $ fuck
mkdir somedir && cd somedir
/etc/somedir $ 
Duncan X Simpson
fonte