AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 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

读取 stdin 上的路径并为每一行生成一个新的交互式 shell

  • 772

考虑一个在整个主目录中搜索具有错误权限的文件或目录的命令:

$ find $HOME -perm 777

这只是一个例子;该命令可能会列出损坏的符号链接:

$ find $HOME -xtype l

或列出冗长的符号链接:

$ symlinks -s -r $HOME

或任何数量的其他昂贵的命令,将换行符分隔的路径发送到stdout.

现在,我可以在这样的寻呼机中收集结果:

$ find $HOME -perm 777 | less

然后cd到不同虚拟终端中的相关目录。但我宁愿有一个脚本为每一行输出打开一个新的交互式 shell,如下所示:

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

这样我可以检查每个文件或目录,检查时间戳,决定是否需要更改权限或删除文件等。

使用从文件或 stdin 读取路径的 bash 脚本是可行的,如下所示:

#! /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}"

这有一些好处,例如:

  • 一旦出现新的输出行,脚本就会打开一个新的 shell stdin。这意味着在我开始做事之前,我不必等待慢速命令完全完成。

  • 当我在新生成的 shell 中执行操作时,slow 命令一直在后台运行,因此下一条路径可能在我完成时准备好访问。

  • 如有必要,我可以提前退出循环,例如使用false; exitCtrl-C Ctrl-D。

  • 该脚本处理文件名和目录。

  • 该脚本避免连续两次导航到同一目录。(感谢@MichaelHomer 解释了如何使用关联数组来做到这一点。)

但是,这个脚本有一个问题:

  • 如果最后一个命令具有非零状态,则整个管道退出,这对于提前退出很有用,但通常需要$?每次检查以防止意外提前退出。

为了尝试解决这个问题,我编写了一个 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))

但是,我在将Continue? (y/n)提示的回复传递给生成的 shell 时遇到了一些问题,导致类似y: command not found. 我怀疑问题出在这条线上:

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

stdin使用时我需要做一些不同的事情subprocess.call吗?

或者,是否有一个广泛可用的工具可以使两个脚本变得多余,而我只是没有听说过?

shell python
  • 2 2 个回答
  • 628 Views

2 个回答

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

    您的 Bash 脚本似乎正在按预期执行所有操作,它只需要|| break在生成交互式 shell 的子 shell 之后出现:这样,当您从该交互式 shell 退出并引发错误时,例如Ctrl+C紧跟 aCtrl+D或exit 1命令,您退出从整个管道早期。

    当然,正如您所指出的,当您从交互式 shell 使用的最后一个命令以(不需要的)错误退出时,它也会退出,但是您可以通过在任何正常 exit:之前发出简单的 as last 命令来轻松规避这种情况,或者也许(作为一个可能更好的解决方案)通过测试作为退出整个管道的唯一可接受的方式,即在产生交互式外壳的子外壳之后使用(而不是仅仅)。Ctrl+C|| { [ $? -eq 130 ] && break; }|| break

    作为一种根本不需要关联数组的更简单的方法,您可能只是-uniq输出find如下所示:

    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
    )
    

    当然,这需要一个产生连续重复(如果有的话)的名称源,就像这样find 做一样。或者您可以使用sort -u 而不是重新排序它们uniq,但是您必须等待sort完成,然后才能看到第一个交互式外壳生成,这是您似乎不希望的壮举。

    然后让我们看看 Python 脚本方法。

    你没有说你是如何调用它的,但是如果你通过管道使用它,如下所示:

    names-source-cmd | visit-paths.py
    

    那么您将 stdin 用于两个相互冲突的目的:输入名称和输入 Pythoninput()函数。

    然后,您可能想要调用您的 Python 脚本,例如:

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

    请注意上面示例中所做的重定向:我们首先将刚刚创建的管道(在管道的那一部分中将是标准输入)重定向到任意文件描述符 3,然后将标准输入重新打开到 tty 以便 Python 脚本可以使用它的input()功能。然后通过 Python 脚本的参数将文件描述符 3 用作名称的来源。

    您还可以考虑以下概念验证:

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

    上面的示例使用了相同的重定向技巧。因此,您可以将它用于您自己的 Bash 脚本,该脚本将看到的路径缓存在关联数组中,并在每个新看到的路径上生成一个交互式 shell。

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

    作为后续,python脚本可以这样修复:

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

    有关的:

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

    https://stackoverflow.com/questions/7141331/pipe-in​​put-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

相关问题

  • 这个命令是如何工作的?mkfifo /tmp/f; 猫/tmp/f | /bin/sh -i 2>&1 | 数控 -l 1234 > /tmp/f

  • FreeBSD 的 sh:列出函数

  • 有没有办法让 ls 只显示某些目录的隐藏文件?

  • grep -v grep 有什么作用

  • 如何将带有〜的路径保存到变量中?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

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

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve