標準入力のパスを読み、各行に対して新しい対話型シェルを作成します。

標準入力のパスを読み、各行に対して新しい対話型シェルを作成します。

ホームディレクトリ全体で無効な権限を持つファイルまたはディレクトリを検索するコマンドを検討してください。

$ find $HOME -perm 777

これは単なる例です。このコマンドは、壊れたシンボリックリンクを一覧表示できます。

$ find $HOME -xtype l

または、長いシンボリックリンクをリストします。

$ symlinks -s -r $HOME

または改行で区切られたパスをstdout

これで、ポケットベルから結果を収集できます。

$ find $HOME -perm 777 | less

その後、cd他の仮想端末の関連ディレクトリに移動します。しかし、私は次のように、各出力行に対して新しいインタラクティブシェルを開くスクリプトを持ちたいと思います。

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

これにより、各ファイルまたはディレクトリを調べ、タイムスタンプを確認し、権限を変更する必要があるか、ファイルを削除する必要があるかを判断できます。

それ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}"

これには、次のようないくつかの利点があります。

  • 新しい出力行が表示されると、スクリプトは新しいシェルを開きますstdin。つまり、ジョブを開始する前に遅いコマンドが完全に完了するのを待つ必要はありません。

  • 新しく作成されたシェルでタスクを実行すると、遅いコマンドはバックグラウンドで実行され続けるため、タスクが完了したときは次のパスにアクセスする準備ができます。

  • false; exit必要に応じて、Ctrl-C Ctrl-Dなどを使用してループを早く終了できます。

  • このスクリプトはファイル名とディレクトリを処理します。

  • このスクリプトは、同じディレクトリを2回連続して移動するのを防ぎます。 (連想配列を使用してこれを行う方法を説明した@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)ただし、生成されたシェルにプロンプ​​ト応答を渡すにはいくつかの問題があり、次のy: command not found問題が発生しているようです。

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

stdin使用時に他の作業を行う必要がありますかsubprocess.call

それとも、私が聞いたことのないこれらの2つのスクリプトを重複させるための広く利用可能なツールはありますか?

ベストアンサー1

Bashスクリプトは期待どおりにすべてのことをするようです。インタラクティブシェルを作成するサブシェルの後にのみ存在する必要があります|| break。これにより、対応するインタラクティブシェルがシャットダウンし、誘導されたエラー(たとえば、Ctrl+CCtrl+Dまたはexit 1コマンド)が発生した場合は、パイプライン全体でシャットダウンされます。

もちろん、指摘したように、対話型シェルで使用された最後のコマンドが(不要な)エラーで終了したときにも終了しますが、次のように単純なコマンドを実行してこれを実行できます:最後のコマンド何よりも先に正常終了Ctrl+Cあるいは、パイプライン全体をシャットダウンする唯一の許容可能な方法でテストすることによって(潜在的に良い解決策として)、|| { [ $? -eq 130 ] && break; }つまり対話型シェルを作成するサブシェルの後に使用されます。|| break

連想配列がまったく必要ない、より簡単な方法で、次のようにuniq出力を-ingできます。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

input()次に、名前入力とPython関数入力という2つの矛盾する目的でstdinを使用しています。

その後、次のようにPythonスクリプトを呼び出すことができます。

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

上記の例で行われたリダイレクトに注意してください。まず、作成したパイプ(パイプのその部分でstdinになる)を任意のファイル記述子3にリダイレクトし、次にPythonスクリプトが使用できるようにstdinをttyとして開きます。そのinput()機能のため。その後、ファイル記述子3はPythonスクリプトの引数を介して名前のソースとして使用されます。

次の概念証明を考慮することもできます。

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

上記の例では、同じリダイレクトトリックを使用しています。したがって、連想配列に示されているパスをキャッシュし、新しく表示された各パスに対話型シェルを生成する独自のBashスクリプトでそれを使用できます。

おすすめ記事