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


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.