AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 509559
Accepted
Nathaniel M. Beaver
Nathaniel M. Beaver
Asked: 2019-03-30 21:47:53 +0800 CST2019-03-30 21:47:53 +0800 CST 2019-03-30 21:47:53 +0800 CST

Leia os caminhos no stdin e gere um novo shell interativo para cada linha

  • 772

Considere um comando que pesquisa em todo o diretório inicial um arquivo ou diretório com as permissões erradas:

$ find $HOME -perm 777

Este é apenas um exemplo; o comando pode estar listando links simbólicos quebrados:

$ find $HOME -xtype l

ou listando links simbólicos longos:

$ symlinks -s -r $HOME

ou qualquer número de outros comandos caros que enviam caminhos delimitados por nova linha para stdout.

Agora, eu poderia reunir os resultados em um pager como este:

$ find $HOME -perm 777 | less

e, em seguida, cdpara os diretórios relevantes em um terminal virtual diferente. Mas eu prefiro ter um script que abra um novo shell interativo para cada linha de saída, assim:

$ find $HOME -perm 777 | visit-paths.sh

Dessa forma, posso, por exemplo, inspecionar cada arquivo ou diretório, verificar o carimbo de data/hora, decidir se preciso alterar as permissões ou excluir arquivos, etc.

É possível com um script bash que lê caminhos de um arquivo ou de stdin , assim:

#! /usr/bin/env bash

set -e

declare -A ALREADY_SEEN
while IFS='' read -u 10 -r line || test -n "$line"
do
    if test -d "$line"
    then
        VISIT_DIR="$line"
    elif test -f "$line"
    then
        VISIT_DIR="$(dirname "$line")"
    else
        printf "Warning: path does not exist: '%s'\n" "$line" >&2
        continue
    fi
    if test "${ALREADY_SEEN[$VISIT_DIR]}" != '1'
    then
        ( cd "$VISIT_DIR" && $SHELL -i </dev/tty )
        ALREADY_SEEN[${VISIT_DIR}]=1
        continue
    else
        # Same as last time, skip it.
        continue
    fi
done 10< "${*:-/dev/stdin}"

Isso tem alguns pontos positivos, como:

  • O script abre um novo shell assim que uma nova linha de saída aparece no stdin. Isso significa que não preciso esperar que o comando slow termine completamente antes de começar a fazer as coisas.

  • O comando slow continua sendo executado em segundo plano enquanto estou fazendo coisas no shell recém-gerado, então o próximo caminho está potencialmente pronto para ser visitado quando eu terminar.

  • Eu posso sair do loop mais cedo, se necessário, com, por exemplo false; exit, ou apenas Ctrl-C Ctrl-D.

  • O script lida com nomes de arquivos e diretórios.

  • O script evita navegar para o mesmo diretório duas vezes seguidas. (Obrigado a @MichaelHomer por explicar como fazer isso com arrays associativos.)

No entanto, há um problema com este script:

  • Todo o pipeline é encerrado se o último comando tiver um status diferente de zero, o que é útil para sair antecipadamente, mas em geral requer verificação a $?cada vez para evitar uma saída antecipada acidental.

Para tentar resolver esse problema, escrevi um script Python:

#! /usr/bin/env python3

import argparse
import logging
import os
import subprocess
import sys

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Visit files from file or stdin.'
    )
    parser.add_argument(
        '-v',
        '--verbose',
        help='More verbose logging',
        dest="loglevel",
        default=logging.WARNING,
        action="store_const",
        const=logging.INFO,
    )
    parser.add_argument(
        '-d',
        '--debug',
        help='Enable debugging logs',
        action="store_const",
        dest="loglevel",
        const=logging.DEBUG,
    )
    parser.add_argument(
        'infile',
        nargs='?',
        type=argparse.FileType('r'),
        default=sys.stdin,
        help='Input file (or stdin)',
    )
    args = parser.parse_args()
    logging.basicConfig(level=args.loglevel)
    shell_bin = os.environ['SHELL']
    logging.debug("SHELL = '{}'".format(shell_bin))
    already_visited = set()
    n_visits = 0
    n_skipped = 0
    for i, line in enumerate(args.infile):
        visit_dir = None
        candidate = line.rstrip()
        logging.debug("candidate = '{}'".format(candidate))
        if os.path.isdir(candidate):
            visit_dir = candidate
        elif os.path.isfile(candidate):
            visit_dir = os.path.dirname(candidate)
        else:
            logging.warning("does not exist: '{}'".format(candidate))
            n_skipped +=1
            continue
        if visit_dir is not None:
            real_dir = os.path.realpath(visit_dir)
        else:
            # Should not happen.
            logging.warning("could not determine directory for path: '{}'".format(candidate))
            n_skipped +=1
            continue
        if visit_dir in already_visited:
            logging.info("already visited: '{}'".format(visit_dir))
            n_skipped +=1
            continue
        elif real_dir in already_visited:
            logging.info("already visited: '{}' -> '{}'".format(visit_dir, real_dir))
            n_skipped +=1
            continue
        if i != 0:
            try :
                response = input("#{}. Continue? (y/n) ".format(n_visits + 1))
            except EOFError:
                sys.stdout.write('\n')
                break
            if response in ["n", "no"]:
                break
        logging.info("spawning '{}' in '{}'".format(shell_bin, visit_dir))
        run_args = [shell_bin, "-i"]
        subprocess.call(run_args, cwd=visit_dir, stdin=open('/dev/tty'))
        already_visited.add(visit_dir)
        already_visited.add(real_dir)
        n_visits +=1

    logging.info("# paths received: {}".format(i + 1))
    logging.info("distinct directories visited: {}".format(n_visits))
    logging.info("paths skipped: {}".format(n_skipped))

No entanto, estou tendo alguns problemas com as respostas ao Continue? (y/n)prompt sendo passado para o shell que é gerado, causando erros como y: command not found. Suspeito que o problema esteja nesta linha:

subprocess.call(run_args, cwd=visit_dir, stdin=open('/dev/tty'))

Preciso fazer algo diferente com o stdinao usar subprocess.call?

Como alternativa, existe uma ferramenta amplamente disponível que torna os dois scripts redundantes da qual eu não ouvi falar?

shell python
  • 2 2 respostas
  • 628 Views

2 respostas

  • Voted
  1. Best Answer
    LL3
    2019-03-31T17:53:02+08:002019-03-31T17:53:02+08:00

    Seu script Bash parece estar fazendo tudo como pretendido, ele só precisa de um || breakapós o subshell que gera o shell interativo: dessa forma, quando você sai desse shell interativo com um erro induzido como Ctrl+Cimediatamente seguido por um Ctrl+Dou um exit 1comando, você sai cedo de todo o pipeline.

    Isso, é claro, como você observou, fará com que ele saia também quando o último comando que você usou do shell interativo sair com um erro (indesejado), mas você pode facilmente contornar isso emitindo um simples :como último comando antes de qualquer saída normal , ou talvez (como uma solução possivelmente melhor) testando Ctrl+Ccomo a única maneira aceita de encerrar todo o pipeline, ou seja, usando || { [ $? -eq 130 ] && break; }(em vez de apenas || break) após o subshell que gera o shell interativo.

    Como uma abordagem muito mais simples que não requer matrizes associativas, você pode apenas uniq-ing a saída findcomo em:

    find . -perm 777 -printf '%h\n' | uniq | \
    (
    while IFS= read -r path ; do
        (cd "${path}" && PS1="[*** REVISE \\w]: " bash --norc -i </dev/tty) || \
            { [ $? -eq 130 ] && break; }
    done
    )
    

    Claro que isso requer uma fonte de nomes que produza duplicatas consecutivas (quando houver), como find faz. Ou você pode reordená-los usando sort -u em vez de uniq, mas então você teria que esperar o sorttérmino, antes de ver o primeiro spawn de shell interativo, o que é um feito que você parece não desejar.

    Vamos então ver a abordagem do script Python.

    Você não diz como está invocando, mas se estiver usando através de um pipe como em:

    names-source-cmd | visit-paths.py
    

    então você está usando stdin para dois propósitos conflitantes: entrada para nomes e entrada para a input()função do seu Python.

    Você pode então querer invocar seu script Python como em:

    names-source-cmd | visit-paths.py /dev/fd/3 3<&0 < /dev/tty
    

    Observe os redirecionamentos feitos no exemplo acima: primeiro redirecionamos o pipe recém-criado (que será stdin nessa parte do pipeline) para o descritor de arquivo arbitrário 3 e, em seguida, reabrimos stdin no tty para que o script Python possa usar isso por sua input()função. O descritor de arquivo 3 é então usado como fonte de nomes por meio do argumento do seu script Python.

    Você também pode considerar a seguinte prova de conceito:

    find | \
    (
    while IFS= read -ru 3 name; do
        echo "name is ${name}"
        read -p "Continue ? " && [ "$REPLY" = y ] || break
    done 3<&0 < /dev/tty
    )
    

    O exemplo acima usa o mesmo truque de redirecionamento. Você pode, portanto, usá-lo para seu próprio script Bash, aquele que armazena em cache os caminhos vistos em matrizes associativas e gera um shell interativo em cada caminho recém-visto.

    • 1
  2. Nathaniel M. Beaver
    2020-05-29T04:34:42+08:002020-05-29T04:34:42+08:00

    Apenas como acompanhamento, o script python pode ser corrigido assim:

    if args.infile == sys.stdin:
        old_stdin = sys.stdin
        sys.stdin = open('/dev/tty')
        args.infile = old_stdin
    

    Relacionado:

    https://stackoverflow.com/questions/5986544/cannot-launch-interactive-program-while-piping-to-script-in-python

    https://stackoverflow.com/questions/7141331/pipe-input-to-python-program-and-later-get-input-from-user

    https://stackoverflow.com/questions/8034595/python-raw-input-following-sys-stdin-read-throws-eoferror

    https://stackoverflow.com/questions/40270252/eoferror-when-using-input-after-using-sys-stdin-buffer-read

    https://bugs.python.org/issue512981

    https://bugs.python.org/issue29396

    • 0

relate perguntas

  • Como funciona este comando? mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc -l 1234 > /tmp/f

  • FreeBSD's sh: funções de lista

  • Existe uma maneira de fazer ls mostrar arquivos ocultos apenas para determinados diretórios?

  • o que grep -v grep faz

  • Como salvar um caminho com ~ em uma variável?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve