파이프에서 스크립트를 사용할 때 사용자 입력을 읽는 방법


10

일반적인 문제

파이프 체인의 중간에 있더라도 사용자와 상호 작용하는 스크립트를 작성하고 싶습니다.

구체적인 예

구체적으로, 그것은 취 file하거나 stdin(행 번호), 디스플레이 라인을 입력하도록 요청하는 선택 또는 광고 번호, 다음에 대응하는 라인을 인쇄 stdout. 이 스크립트를 호출 해 봅시다 selector. 그런 다음 기본적으로 할 수 있기를 원합니다

grep abc foo | selector > myfile.tmp

foo포함하는 경우

blabcbla
foo abc bar
quux
xyzzy abc

그런 다음 옵션이 selector있는 터미널이 아닌 터미널에 표시됩니다 myfile.tmp!

1) blabcbla
2) foo abc bar
3) xyzzy abc
Select options:

그 후 내가 입력

2-3

그리고 결국

foo abc bar
xyzzy abc

의 내용으로 myfile.tmp.

선택기 스크립트가 설치되어 실행 중이며 기본적으로 입력 및 출력을 리디렉션하지 않으면 완벽하게 작동합니다. 그래서

selector foo

내가 원하는 것처럼 행동합니다. 그러나 위 예제와 같이 함께 파이핑 할 때 selector제시된 옵션을 인쇄 myfile.tmp하고 grepped 입력에서 선택 항목을 읽으려고합니다.

내 접근 방식

-u에서 read와 같이 의 플래그 를 사용하려고 했습니다.

exec 4< /proc/$PPID/fd/0
exec 4> /proc/$PPID/fd/1
nl $INPUT >4
read -u4 -p"Select options: "

그러나 이것은 내가 원하는 바를 수행하지 않습니다.

Q : 실제 사용자 상호 작용은 어떻게 받습니까?


스크립트를 만들고 출력을 변수에 저장 한 다음 원하는 사용자를 원하십니까 ??
Hackaholic

@Hackaholic — 무슨 말인지 잘 모르겠습니다. 모든 종류의 파이프 라인 시퀀스 (예 : Unix 방식)에 배치 할 수있는 스크립트를 원합니다. 위의 정교한 예를 들었지만 분명히 내가 유일하게 사용하는 사례는 아닙니다.
jmc

1
사용cmd | { some processing; read var </dev/tty; } | cmd
mikeserv

@mikeserv — 흥미 롭습니다! 나는 지금 alias selector='{ TMPFILE=$(mktemp); cat > $TMPFILE; nl -s") " $TMPFILE | column -c $(tput cols); read -e -p"Select options: " < /dev/tty; rangeselect -v range="$REPLY" $TMPFILE; rm $TMPFILE; }'꽤 잘 작동합니다. 그러나 grep b foo | selector | wc -l여기서 넘어집니다. 그것을 고치는 방법에 대한 아이디어가 있습니까? 그건 그렇고, rangeselect내가 사용한 것은 pastebin.com/VAxTSSHs 에서 찾을 수 있습니다 . 주어진 행 번호 범위에 해당하는 파일의 행을 인쇄하는 간단한 AWK 스크립트입니다. (범위는 "3-10, 12,14,16-20"와 같은 것이 될 수 있습니다.)
jmc

1
alias오히려 selector() { all of that stuff...; }기능에 들어 가지 마십시오 . aliases는 단순 명령의 이름을 바꾸는 반면 함수는 복합 명령 을 단일 단순 명령으로 묶습니다 .
mikeserv

답변:


8

/proc/$PPID/fd/0신뢰할 수없는 사용 : selector프로세스 의 부모가 터미널을 입력으로 사용하지 못할 수 있습니다.

항상 현재 프로세스의 터미널을 참조 하는 표준 경로/dev/tty있습니다.

nl "$INPUT" >/dev/tty
read -p"Select options: " </dev/tty

또는

exec </dev/tty >/dev/tty
nl "$INPUT"
read -p"Select options: "

1
고마워, 내 문제를 해결합니다. 대답은 조금 최소한입니다. 질문에 대한 의견에 mikeserv의 조언을 통합하면 도움이 될 것입니다.
jmc

2

나는 작은 기능을 작성했다 : 그것은 당신이 파이프 체인에 요청한 것에 대해서는 대답하지 않지만 문제를 해결할 것입니다.

inf() ( [ -n "$ZSH_VERSION" ] && emulate sh
        unset n i c; set -f; tab='      ' IFS='
';      _in()   until [ "$((i+=1))" -gt 5 ] && exit 1
                printf '\nSelect: '
                read -r c && [ -n "${c##*[!- 0-9]*}" ]
                do echo "Invalid selection."
                done
        _out()  for n do i=; [ "$n" = . ]  &&
                printf '"${%d#*$tab}" ' $c ||
                until c="${c#*.} ${i:=${n%%-*}}"
                [ "$((i+=1))" -gt "${n#*-}" ]
                do :; done; done
set -- $(grep "$@"|nl -w1 -s "$tab"|tee /dev/tty)
i=$((($#<1)*5)); _in </dev/tty >/dev/tty
eval "printf '%s\n' $(c=$c\ . IFS=\ ;_out $c)"
)

이 함수는 사용자가 즉시 제공하는 모든 인수를 뒤집습니다 grep. 쉘 glob을 사용하여 읽을 파일을 지정하면 glob 순서의 첫 번째로 시작하여 마지막 일치로 끝나는 모든 파일의 모든 일치 항목을 리턴합니다.

grep그 출력 전달 nl각 라인이되는 그 출력 통과하는 숫자 tee를 모두 출력하는 중복 stdout하고이 /dev/tty. 이는 파이프 라인의 출력이 함수의 인수 배열 \n과 함께 ewline 으로 분할되고 작동 할 때 터미널로 동시에 인쇄 됨을 의미합니다.

다음으로 _in()함수 read는 이전 조치의 결과가 최대 5 회 이상인 경우 선택을 시도합니다 . 선택은 공백으로 구분 된 숫자 만 또는 숫자로 구분 된 숫자 범위로 구성 될 수 있습니다 -. read 빈 줄을 포함하여 다른 것이 있으면 다시 시도하지만 이전과 마찬가지로 최대 5 번 시도합니다.

마지막으로 _out()함수는 사용자의 선택을 구문 분석하고 그 범위를 확장합니다. 결과 "${[num]}"를 각각 의 형식으로 인쇄하여 inf()arg 배열에 저장된 행의 값과 일치시킵니다 . 이 출력은 eval인수로 printf사용되므로 사용자가 선택한 행만 인쇄합니다.

그것은 분명히 read터미널에서 왔으며 Select:메뉴 만 인쇄 stderr하므로 파이프 라인 친화적입니다. 예를 들어 다음이 작동합니다.

seq 100 |inf 3|grep 8
1       3
2       13
3       23
4       30
5       31
6       32
7       33
8       34
9       35
10      36
11      37
12      38
13      39
14      43
15      53
16      63
17      73
18      83
19      93

Select: 6 9 12-18
38
83

그러나 원하는 옵션 grep과 원하는 수의 파일 이름을 사용할 수 있습니다. 즉, $IFS빈 줄을 검색하는 경우 구문 분석 입력의 부작용으로 한 가지 유형을 제외한 모든 유형을 사용할 수 있습니다 . 그러나 번호가 매겨진 빈 줄 목록에서 누가 선택하고 싶습니까?

마지막으로 이것은 숫자 사용자 입력을 함수의 인수 배열에 저장된 숫자 위치 매개 변수로 직접 변환하여 작동하므로 사용자가 선택한 횟수, 사용자가 선택한 횟수 및 사용자가 선택한 순서대로 출력이 출력됩니다 그것.

예를 들면 다음과 같습니다.

seq 1000 | inf 00\$

1       100
2       200
3       300
4       400
5       500
6       600
7       700
8       800
9       900
10      1000

Select: 4-8 1 1 3-6
400
500
600
700
800
100
100
300
400
500
600

@ mikeserv 그것은 전체 스크립트가 아니라 단지 아이디어 일뿐입니다. 테스트에 대해 이야기하는 것은 원본 파일이 디스크에만 있기 때문에 당신은 그것들을 가져옵니다. 그래서 그것을 테스트하는 것이 문제가 아니거나 추가 노력이 아니라고 생각합니다
Hackaholic

@ mikeserv 그래 맞아, 나는 부적절한 입력과 모든 것과 같은 모든 것을 검증하지 않았다. 포인트 감사합니다
Hackaholic

@ mikeserv 나는 쉘 프로그래밍의 모든 기본을 알고, 나에게 고급에 갈 수있는 방법을 안내 할 수
있습니까

네, 확실히 편집 해
드리겠습니다
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.