표준 입력의 경로를 읽고 각 줄에 대해 새로운 대화형 셸을 생성합니다.

표준 입력의 경로를 읽고 각 줄에 대해 새로운 대화형 셸을 생성합니다.

전체 홈 디렉터리에서 잘못된 권한이 있는 파일이나 디렉터리를 검색하는 명령을 고려해 보세요.

$ 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 등을 사용하여 루프를 일찍 종료할 수 있습니다.

  • 이 스크립트는 파일 이름과 디렉터리를 처리합니다.

  • 이 스크립트는 동일한 디렉터리를 두 번 연속 탐색하는 것을 방지합니다. (연관 배열을 사용하여 이 작업을 수행하는 방법을 설명한 @MichaelHomer에게 감사드립니다.)

그러나 이 스크립트에는 문제가 있습니다.

  • 마지막 명령의 상태가 0이 아닌 경우 전체 파이프라인이 종료됩니다. 이는 조기 종료에 유용하지만 일반적으로 $?우발적인 조기 종료를 방지하기 위해별 확인이 필요합니다.

이 문제를 해결하기 위해 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?

아니면 제가 들어보지 못한 이 두 스크립트를 중복되게 만드는 널리 사용 가능한 도구가 있습니까?

답변1

Bash 스크립트는 예상대로 모든 작업을 수행하는 것 같습니다. 대화형 셸을 생성하는 하위 셸 뒤에만 있어야 합니다 || break. 이렇게 하면 해당 대화형 셸을 종료하고 유도된 오류(예: Ctrl+C다음 Ctrl+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 함수 입력 이라는 두 가지 상충되는 목적으로 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 스크립트와 함께 이를 사용할 수 있습니다.

답변2

관련 정보