Por que shell builtins não têm páginas de manual adequadas?

32

Todos os shell shell compartilham a mesma página de manual:

BUILTIN(1)                BSD General Commands Manual               BUILTIN(1)

NAME
     builtin, !

etc.

Depois, há um pequeno texto descrevendo o que são os shellins integrados e, em seguida, uma lista que se parece com isso:

  Command       External    csh(1)    sh(1)
       !             No          No        Yes
       %             No          Yes       No

Mas se conseguirmos, man greptemos seções como

  • Insetos
  • História
  • Veja também
  • Padrões
  • Descrição

etc.

Os shell shell não têm sua própria história, descrição e argumentos como -Aou -r? Por que isso não é fornecido nas páginas de manual e como eu aprenderia a usá-las de maneira correta e eficiente?

Mostrar nome
fonte

Respostas:

25

Porque os componentes internos fazem parte do shell. Quaisquer bugs ou histórico que eles tiverem são bugs e histórico do próprio shell. Eles não são comandos independentes e não existem fora do shell em que estão integrados.

O equivalente, bashpelo menos, é o helpcomando. Por exemplo:

$ help while
while: while COMMANDS; do COMMANDS; done
    Execute commands as long as a test succeeds.

    Expand and execute COMMANDS as long as the final command in the
    `while' COMMANDS has an exit status of zero.

    Exit Status:
    Returns the status of the last command executed.

Todos os bash builtins têm helppáginas. Mesmo helpele mesmo :

$ help help
help: help [-dms] [pattern ...]
    Display information about builtin commands.

    Displays brief summaries of builtin commands.  If PATTERN is
    specified, gives detailed help on all commands matching PATTERN,
    otherwise the list of help topics is printed.

    Options:
      -d    output short description for each topic
      -m    display usage in pseudo-manpage format
      -s    output only a short usage synopsis for each topic matching
        PATTERN

    Arguments:
      PATTERN   Pattern specifiying a help topic

    Exit Status:
    Returns success unless PATTERN is not found or an invalid option is given.

Inspirado no sedscript de @ mikeserv , aqui está uma pequena função que imprimirá a seção relevante de uma página de manual usando Perl. Adicione esta linha ao arquivo de inicialização do seu shell ( ~/.bashrcpara bash):

manperl(){ man "$1" | perl -00ne "print if /^\s*$2\b/"; }

Em seguida, você o executa fornecendo uma página de manual e o nome de uma seção:

$ manperl bash while
       while list-1; do list-2; done
       until list-1; do list-2; done
              The while command continuously executes the list list-2 as long as the last command in the list list-1 returns an exit
              status of zero.  The until command is identical to the while command, except that the test is negated; list-2 is  exe‐
              cuted  as  long  as the last command in list-1 returns a non-zero exit status.  The exit status of the while and until
              commands is the exit status of the last command executed in list-2, or zero if none was executed.

$ manperl grep SYNOPSIS
SYNOPSIS
       grep [OPTIONS] PATTERN [FILE...]
       grep [OPTIONS] [-e PATTERN | -f FILE] [FILE...]

$ manperl rsync "-r"
       -r, --recursive
              This tells rsync to copy directories recursively.  See also --dirs (-d).
terdon
fonte
2
@DisplayName eles são bash. Eles fazem parte disso e sim, são explicados na SHELL BUILTIN COMMANDSseção da bashpágina de manual. Suas "páginas de manual" são help builtin_name.
terdon
3
O que não está claro é por que eles não receberam páginas de manual. As páginas de manual são apenas arquivos no MANPATH. Eles não precisam corresponder a binários separados. Em princípio, não há razão para que o bash não possa ter sido enviado com páginas de manual para seus componentes - em vez de ter um sistema de ajuda interno.
9788 Francis Davey #
4
@FrancisDavey: Mas a maioria dos builtins existe (com extensões diferentes) em várias conchas. Manpages não são específicas do shell; eles são de todo o sistema.
rici
2
@FrancisDavey Como disse rici, os comandos não são de todo o sistema. Seria um pouco enganador ter uma página de manual para um comando que não está presente em todos os shell, mas, pior ainda, seria muito confuso ter uma página de manual para um comando que está presente em vários shells, mas que se comporta de maneira diferente (por exemplo, , aceita argumentos diferentes, tem sintaxe diferente etc.).
21413 Joshua Taylor #:
1
@mikeserv No entanto, eu gostaria de receber as páginas de manual para os shell embutidos, de acordo com o que, por exemplo, o git oferece, para onde man git commita página de manual é exibida git-commit. Algo como man bash ifseria maravilhoso .
21413 Joshua Taylor #:
5

Embora seja verdade que alguns componentes internos do shell podem ter pouca exibição em um manual completo - especialmente para aqueles bashcomponentes específicos que você provavelmente só usa em um sistema GNU (o pessoal do GNU, por via de regra, não acredita mane preferem suas próprias infopáginas) - a grande maioria dos utilitários POSIX - embutidos no shell ou outros - estão muito bem representados no Guia do Programador do POSIX.

Aqui está um trecho da parte inferior do meu man sh (que provavelmente tem 20 páginas ou mais ...)

insira a descrição da imagem aqui

Todos esses estão lá, e outros não mencionados, como set, read, break... bem, eu não preciso nomeá-los todos. Mas observe o (1P)canto inferior direito - denota a série de manuais da categoria 1 do POSIX - são essas as manpáginas das quais estou falando.

Pode ser que você só precise instalar um pacote? Isso parece promissor para um sistema Debian. Embora helpseja útil, se você puder encontrá-lo, definitivamente deverá obter essa POSIX Programmer's Guidesérie. Pode ser extremamente útil. E suas páginas constituintes são muito detalhadas.

Além disso, os buildins do shell quase sempre são listados em uma seção específica do manual do shell específico. zsh, por exemplo, tem uma manpágina inteira separada para isso (acho que totaliza 8 ou 9 zshpáginas individuais ), incluindo o zshallque é enorme.

Você pode, é grep manclaro:

man bash 2>/dev/null | 
grep '^[[:blank:]]*read [^`]*[-[]' -A14

   read [-ers] [-a aname] [-d  delim]  [-i  text]  [-n
   nchars]  [-N  nchars]  [-p prompt] [-t timeout] [-u
   fd] [name ...]
          One line is read from the standard input, or
          from  the  file descriptor fd supplied as an
          argument to the -u  option,  and  the  first
          word is assigned to the first name, the sec‐
          ond word to the second name, and so on, with
          leftover words and their intervening separa‐
          tors assigned to the last  name.   If  there
          are  fewer  words read from the input stream
          than names, the remaining names are assigned
          empty  values.   The  characters  in IFS are
          used to split the line into words using  the
          same  rules  the  shell  uses  for expansion

... o que é bem parecido com o que costumava fazer ao pesquisar uma manpágina de shell . Mas helpé muito bom bashna maioria dos casos.

Na verdade, eu tenho trabalhado em um sedscript para lidar com esse tipo de coisa recentemente. Foi assim que peguei a seção na foto acima. Ainda é mais longo do que eu gosto, mas está melhorando - e pode ser bastante útil. Em sua iteração atual, ele extrai de maneira bastante confiável uma seção de texto sensível ao contexto, correspondente a uma seção ou cabeçalho de subseção com base nos [a] padrões [s] dados na linha de comando. Ele colore sua saída e imprima em stdout.

Ele funciona avaliando os níveis de recuo. Linhas de entrada não em branco geralmente são ignoradas, mas quando encontra uma linha em branco, começa a prestar atenção. Ele reúne linhas a partir daí até verificar que a sequência atual definitivamente recua ainda mais do que sua primeira linha antes que outra linha em branco ocorra, ou então deixa cair o segmento e aguarda o próximo espaço em branco. Se o teste for bem-sucedido, ele tenta combinar a linha de chumbo com seus argumentos da linha de comando.

Isto significa que um jogo padrão irá corresponder:

heading
    match ...
    ...
    ...
        text...

..e..

match
   text

..mas não..

heading
    match
    match

    notmatch

..ou..

         text

         match
         match
         text

         more text

Se houver uma correspondência, ela começa a imprimir. Ele removerá os espaços em branco principais da linha correspondente de todas as linhas impressas - portanto, não importa o nível de indentação encontrado, a linha impressa como se estivesse no topo. Ele continuará sendo impresso até encontrar outra linha em um nível igual ou inferior ao recuo que sua linha correspondente - para que seções inteiras sejam agarradas apenas com uma correspondência de cabeçalho, incluindo todas / todas as subseções, parágrafos que possam conter.

Então, basicamente, se você solicitar que ele corresponda a um padrão, ele o fará apenas contra um cabeçalho de assunto de algum tipo e colorirá e imprimirá todo o texto encontrado na seção encabeçada por sua correspondência. Nada é salvo assim, exceto o recuo da primeira linha - e, portanto, pode ser muito rápido e lidar com \nentradas separadas por linha de ew de praticamente qualquer tamanho.

Levei um tempo para descobrir como recursar em subtítulos como o seguinte:

Section Heading
    Subsection Heading

Mas eu resolvi isso eventualmente.

Eu tive que refazer a coisa toda por uma questão de simplicidade, no entanto. Embora antes eu tivesse vários pequenos loops fazendo basicamente as mesmas coisas de maneiras ligeiramente diferentes para se ajustarem ao seu contexto, variando seus meios de recursão, eu consegui desduplicar a maioria do código. Agora existem dois loops - um imprime e um verifica recuo. Ambos dependem do mesmo teste - o loop de impressão inicia quando o teste passa e o loop de recuo assume o controle quando falha ou inicia em uma linha em branco.

Todo o processo é muito rápido, porque na maioria das vezes /./delimina qualquer linha que não esteja em branco e passa para a próxima - resulta mesmo de zshallpreencher a tela instantaneamente. Isso não mudou.

De qualquer forma, é muito útil até agora, no entanto. Por exemplo, a readcoisa acima pode ser feita como:

mansed bash read

... e fica com o bloco inteiro. Pode levar qualquer padrão ou qualquer outra coisa, ou vários argumentos, embora o primeiro seja sempre a manpágina na qual ele deve pesquisar. Aqui está uma imagem de alguns de seus resultados depois que eu fiz:

mansed bash read printf

insira a descrição da imagem aqui

... os dois blocos são retornados inteiros. Costumo usá-lo como:

mansed ksh '[Cc]ommand.*'

... para o qual é bastante útil. Além disso, obter o SYNOPS[ES]torna realmente útil:

insira a descrição da imagem aqui

Aqui está, se você quiser dar uma guinada - não vou te culpar se não o fizer.

mansed() {
MAN_KEEP_FORMATTING=1 man "$1" 2>/dev/null | ( shift
b='[:blank:]' s='[:space:]' bs=$(printf \\b) esc=$(printf '\033\[') n='\
' match=$(printf "\([${b}]*%s[${b}].*\)*" "$@")
sed -n "1p
    /\n/!{  /./{    \$p;d
        };x;    /.*\n/!g;s///;x
    :indent
        /.*\n\n/{s///;x
        };n;\$p;
        /^\([^${s}].*\)*$/{s/./ &/;h;   b indent
        };x;    s/.*\n[^-[]*\n.*//; /./!x;t
        s/[${s}]*$//;   s/\n[${b}]\{2,\}/${n} /;G;h
    };
    #test
    /^\([${b}]*\)\([^${b}].*\n\)\1\([${b}]\)/!b indent
        s//\1\2.\3/
    :print
    /^[${s}]*\n\./{ s///;s/\n\./${n}/
        /${bs}/{s/\n/ & /g;
            s/\(\(.\)${bs}\2\)\{1,\}/${esc}38;5;35m&${esc}0m/g
            s/\(_${bs}[^_]\)\{1,\}/${esc}38;5;75m&${esc}0m/g
            s/.${bs}//g;s/ \n /${n}/g
            s/\(\(${esc}\)0m\2[^m]*m[_ ]\{,2\}\)\{2\}/_/g
        };p;g;N;/\n$/!D
        s//./;  t print
    };
    #match
        s/\n.*/ /;  s/.${bs}//g
        s/^\(${match}\).*/${n}\1/
        /../{   s/^\([${s}]*\)\(.*\)/\1${n}/
        x;  s//${n}\1${n}. \2/; P
    };D
");}

Resumidamente, o fluxo de trabalho é:

  • qualquer linha que não esteja em branco e que não contenha um \ncaractere ewline será excluída da saída.
    • \nOs caracteres ewline nunca ocorrem no espaço do padrão de entrada. Eles só podem ser obtidos como resultado de uma edição.
  • :printe :indentsão loops fechados mutuamente dependentes e são a única maneira de obter uma linha de \new.
    • :printO ciclo de loop do começa se os caracteres \niniciais de uma linha são uma série de espaços em branco seguidos por um caractere de linha de ew.
    • :indentO ciclo começa em linhas em branco - ou em :printlinhas de ciclo que falham #test- mas :indentremove todas as principais \nseqüências em branco + linha de linha de saída de sua saída.
    • uma vez :printiniciado, ele continuará puxando as linhas de entrada, eliminando os espaços em branco até a quantidade encontrada na primeira linha do seu ciclo, convertendo escapamentos de overstrike e understrike em backspace em escapes terminais coloridos e imprimindo os resultados até #testfalhar.
    • antes do :indentinício, ele primeiro verifica o hespaço antigo quanto a qualquer possível continuação de recuo (como uma subseção) e continua a receber entradas desde que #testfalhe e qualquer linha após a primeira continua a corresponder [-. Quando uma linha após a primeira não corresponde a esse padrão, ela é excluída - e, posteriormente, todas as linhas seguintes são seguidas até a próxima linha em branco.
  • #matche #testcolmatar os dois loops fechados.
    • #testpassa quando a série principal de espaços em branco é menor que a série seguida pela última \nlinha de ew em uma sequência de linhas.
    • #match\nprecede as linhas de linha principais necessárias para iniciar um :printciclo para qualquer uma das :indentseqüências de saída que levam uma correspondência a qualquer argumento da linha de comando. Aquelas seqüências que não são renderizadas em branco - e a linha em branco resultante é passada de volta para :indent.
mikeserv
fonte
2
Seu sed-fu é forte. Claro, você pode fazer a mesma coisa com manperl(){ man $1 | perl -00ne "print if /^\s*$2\b/"; }e, em seguida, manperl sh SYNOPSISou manperl sh read:)
terdon
@terdon - não, você não pode. Isso não inibe a entrada. Eu poderia fazer a mesma coisa assim sed 'H;$!d;g;s/\(\(\n *\)match\([^\n]*\)\2 \)\{1,\}\)*.\{,1\}/\1/g'... provavelmente isso funciona ... mas isso requer engolir o arquivo e analisar tudo de uma vez. Isso funciona em um fluxo - ele pode lidar com entradas de qualquer tamanho, desde que as linhas não sejam astronomicamente longas. Ele é impresso à medida que funciona - e analisa todos manos \bescapes do ackslash para inicializar. Mas mané apenas uma única aplicação para ele - Eu apliquei muito do que a outros problemas, bem ...
mikeserv
1
Estou apenas puxando sua corrente, já que posso fazer o que você descreve com um forro minúsculo. Observe, no entanto, que ele não engole o arquivo inteiro, ele funciona em um fluxo. Ele apenas define "linhas" usando em \n\nvez de, \nmas ainda pode lidar com qualquer tamanho de entrada e imprime à medida que funciona. Veja "modo de parágrafo" aqui: perldoc.perl.org/perlrun.html
terdon
@terdon Talvez essa fosse uma maneira melhor de ir aqui. No sedque pode ser feito como: '/./{H;$!d' -e '};x;now work the paragraph...'. Eu também faço isso frequentemente. Mas originalmente escrevi a primeira parte para assistir a um registro ao vivo por um período ilimitado de tempo, e mesmo esse comportamento era duvidoso - o buffer pode explodir sob certas condições. Isso era apenas metade desse tamanho - mantornava mais difícil. Eu olhei man -Hdepois de obter a mansinop acima, e acho que pode ser mais fácil trabalhar com o HTML gerado por máquina que o groff pode imprimir nos sistemas GNU. Já estou meio que cotovelo já
thg #
@terdon - adivinhei a mim mesmo e tentei uma abordagem centrada em parágrafos, mas é mais fácil como está. Isso recebe seções. Like mansed cmd DESCRIPTIONrecebe a seção DESCRIPTION - e todas as incluídas. Uma pesquisa correspondente é impressa inteira e como se seu nível de recuo fosse o primeiro. Ele até pula falsos positivos ignorando parágrafos que correspondem, mas não recuam ainda mais. Ele combina seus argumentos através das escapes do backspace colorido e não os manipula até que esteja definitivamente pronto para imprimir uma linha. Tudo isso é muito difícil para mim, com muito mais dados do que uma única linha de cada vez.
mikeserv
1

Cada shell possui seu próprio conjunto de componentes. Embora existam pontos em comum, cada um deles tem suas próprias peculiaridades que precisam ser documentadas.

Em sistemas como Linux e FreeBSD (e OSX, que herda do FreeBSD) em que cada shell é fornecido como um pacote separado, não há página de manual para os componentes internos; em vez disso, cada built-in é documentado na página do manual do shell. Portanto, leia a página do manual do bash para obter a documentação do built-in do bash kill, leia a página do manual do dash para a documentação do built-in do dash kill, etc. Há também uma página do manual para o killutilitário independente.

Consulte Posso obter páginas de manual individuais para os comandos bash builtin? para uma manfunção que mostra a documentação interna do bash em vez da página de manual, se o argumento for o nome de um builtin.

Existem variantes unix que fornecem páginas de manual para os shell shell - de fato, a maioria das variantes comerciais fornece. Isso é possível porque o sistema vem com uma única concha ou um conjunto de conchas conhecidas. A página de manual discute as diferenças entre os shells. Por exemplo, a fg(1)página de manual no Solaris 10 possui seções para sh, kshe csh. A fg(1)página de manual no AIX 7.1 faz referência ao "shell Korn" e "shell POSIX", mas os discute juntos (eles suportam exatamente os mesmos recursos para fg). A fg(1)página do manual no Tru64 5.0 discute o ksh embutido e refere os usuários do csh à csh(1)página do manual. SCOaparentemente vem com uma única concha. Você pode instalar outros shells como pacotes complementares nesses sistemas operacionais; se você usar um shell personalizado, lembre-se de que as páginas de manual dos componentes internos não serão relevantes ao usar um shell não padrão.

Gilles 'SO- parar de ser mau'
fonte