전체 홈 디렉터리에서 잘못된 권한이 있는 파일이나 디렉터리를 검색하는 명령을 고려해 보세요.
$ 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
후속 조치로 Python 스크립트를 다음과 같이 수정할 수 있습니다.
if args.infile == sys.stdin:
old_stdin = sys.stdin
sys.stdin = open('/dev/tty')
args.infile = old_stdin
관련된: