Bash 스크립트의 일부를 종료하려면 두 가지 방법이 필요합니다.
카운터가 미리 정의된 숫자에 도달하거나 사용자가 수동으로 스크립트가 카운터의 현재 값을 계속 사용하도록 강제합니다.
구체적으로 - USB 드라이브를 나열했습니다. 15개가 있으면 이를 계산하는 함수가 종료되고 스크립트가 계속될 수 있습니다.
내 코드는 다음과 같습니다.
scannew(){
NEW=0
OLD=$NEW
while [ true ]; do
# count the new drives
lsblk -r > drives.new
diff drives.old drives.new | grep disk | cut -d' ' -f 2 | sort > drives.all
NEW=$(wc -l drives.all | cut -d' ' -f1)
echo -en " Detected drives: $NEW \r"
sleep 0.01
if [ "$NEW" -eq "15" ]; then # exit if we reach the limit
break
fi
done
}
# SOME CODE...
lsblk -r > drives.old
scannew & # start live device counter in the background
SCAN_PID=$! # remember it's PID
wait $SCAN_PID 2>/dev/null # wait until it dies
echo "It's on!"
# REST OF THE CODE...
해당 명령으로 다양한 작업을 시도했지만 read
결과적으로 스크립트는 항상 읽기가 종료될 때까지 기다리고(ENTER 키를 누른 후) "15 제한" 조건을 무시할 수 없습니다.
예를 들어 대신 함수에서 사용해 보았습니다 read -t
.sleep
scannew()
scannew(){
NEW=0
OLD=$NEW
while [ true ]; do
# count the new drives
lsblk -r > drives.new
diff drives.old drives.new | grep disk | cut -d' ' -f 2 | sort > drives.all
NEW=$(wc -l drives.all | cut -d' ' -f1)
echo -en " Detected drives: $NEW \r"
read -t 0.01 -n 1 && break # read instead of sleep
if [ "$NEW" -eq "15" ]; then
break
fi
done
}
그러나 함수 하위 프로세스는 표준 입력에 액세스할 수 없는 것 같으며 read -t 0.01 -n 1 < /dev/stdin && break
이를 사용하면 작동하지 않습니다.
어떻게 해야 하나요?
답변1
내가 가장 먼저 하고 싶은 말은 당신이할 수 있다스크립트의 다른 곳에서 다시 스캔할 계획이 없다면 어쨌든 수행할 것이므로 scannew
포함 된 모든 항목을 인라인하세요 . wait
이는 실제로 통화가 wc
너무 오래 걸릴 수 있다는 우려 사항이며, 그렇다면 통화를 종료할 수 있습니다. 다음은 trap
프로세스로 전송된 신호를 캡처하고 이에 대한 자체 핸들러를 설정할 수 있는 간단한 설정입니다 .
#! /usr/bin/env bash
# print a line just before we run our subshell, so we know when that happens
printf "Lets do something foolish...\n"
# trap SIGINT since it will be sent to the entire process group and we only
# want the subshell killed
trap "" SIGINT
# run something that takes ages to complete
BAD_IDEA=$( trap "exit 1" SIGINT; ls -laR / )
# remove the trap because we might want to actually terminate the script
# after this point
trap - SIGINT
# if the script gets here, we know only `ls` got killed
printf "Got here! Only 'ls' got killed.\n"
exit 0
그러나 작업한 방식을 유지하고 scannew
기능을 백그라운드 작업으로 실행하려면 더 많은 작업을 수행해야 합니다.
사용자 입력이 필요하므로 올바른 접근 방식은 을 사용하는 것입니다. 하지만 사용자 입력을 영원히 기다리지 않고 작업이 완료된 후에도 계속 실행하려면 read
스크립트가 필요합니다 . 이것을 조금 까다롭게 만드는 것은 s가 신호를 처리하도록 허용하기 전에 현재 명령이 완료되기를 기다리는 것입니다. 내가 아는 한, 전체 스크립트를 리팩터링하지 않는 유일한 해결책은 루프를 삽입하고 를 사용하는 것입니다 . 이렇게 하면 프로세스가 완료되는 데 항상 최소 1초가 걸리지만, 귀하와 같은 경우에는 기본적으로 USB 장치를 나열하는 폴링 데몬입니다. 아마도 허용될 것입니다.scannew
read
bash
trap
read
while true
read -t 1
#! /usr/bin/env bash
function slow_background_work {
# condition can be anything of course
# for testing purposes, we're just checking if the variable has anything in it
while [[ -z $BAD_IDEA ]]
do
BAD_IDEA=$( ls -laR / 2>&1 | wc )
done
# `$$` normally gives us our own PID
# but in a subshell, it is inherited and thus
# gives the parent's PID
printf "\nI'm done!\n"
kill -s SIGUSR1 -- $$
return 0
}
# trap SIGUSR1, which we're expecting from the background job
# once it's done with the work we gave it
trap "break" SIGUSR1
slow_background_work &
while true
do
# rewinding the line with printf instead of the prompt string because
# read doesn't understand backslash escapes in the prompt string
printf "\r"
# must check return value instead of the variable
# because a return value of 0 always means there was
# input of _some_ sort, including <enter> and <space>
# otherwise, it's really tricky to test the empty variable
# since read apparently defines it even if it doesn't get input
read -st1 -n1 -p "prompt: " useless_variable && {
printf "Keypress! Quick, kill the background job w/ fire!\n"
# make sure we don't die as we kill our only child
trap "" SIGINT
kill -s SIGINT -- "$!"
trap - SIGINT
break
}
done
trap - SIGUSR1
printf "Welcome to the start of the rest of your script.\n"
exit 0
물론, 정말로 원하는 것이 데몬이거나 USB 장치 수의 변화를 모니터링하는 것이라면 systemd
어느 것이 더 우아한 것을 제공하는지 고려해야 합니다.
답변2
주어진 명령을 실행하고 사용자 입력 시 이를 종료하고 그렇지 않으면 종료하기 위한 (다소) 일반적인 솔루션입니다. 요점은 일종의 원시 터미널 모드에서 읽기를 수행하고, "any" 키를 찾고, 하위 프로세스가 종료될 때 전송되어야 하는 신호를 통해 실행 중인 프로그램 종료 상황을 처리하는 것입니다 SIGCHLD
(하위 프로세스에 흥미로운 것이 없다고 가정). .코드 및 문서(및 최종 테스트).
#ifdef __linux__
#define _POSIX_SOURCE
#include <sys/types.h>
#endif
#include <err.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <termios.h>
#include <unistd.h>
int Flag_UserOkay; // -U
struct termios Original_Termios;
pid_t Child_Pid;
void child_signal(int unused);
void emit_help(void);
void reset_term(void);
int main(int argc, char *argv[])
{
int ch, status;
char anykey;
struct termios terminfo;
while ((ch = getopt(argc, argv, "h?U")) != -1) {
switch (ch) {
case 'U':
Flag_UserOkay = 1;
break;
case 'h':
case '?':
default:
emit_help();
/* NOTREACHED */
}
}
argc -= optind;
argv += optind;
if (argc == 0)
emit_help();
if (!isatty(STDIN_FILENO))
errx(1, "must have tty to read from");
if (tcgetattr(STDIN_FILENO, &terminfo) < 0)
err(EX_OSERR, "could not tcgetattr() on stdin");
Original_Termios = terminfo;
// cfmakeraw(3) is a tad too raw and influences output from child;
// per termios(5) use "Case B" for quick "any" key reads with
// canonical mode (line-based processing) and echo turned off.
terminfo.c_cc[VMIN] = 1;
terminfo.c_cc[VTIME] = 0;
terminfo.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSAFLUSH, &terminfo);
atexit(reset_term);
signal(SIGCHLD, child_signal);
Child_Pid = fork();
if (Child_Pid == 0) { // child
close(STDIN_FILENO);
signal(SIGCHLD, SIG_DFL);
status = execvp(*argv, argv);
warn("could not exec '%s' (%d)", *argv, status);
_exit(EX_OSERR);
} else if (Child_Pid > 0) { // parent
if ((status = read(STDIN_FILENO, &anykey, 1)) < 0)
err(EX_IOERR, "read() failed??");
kill(Child_Pid, SIGTERM);
} else {
err(EX_OSERR, "could not fork");
}
exit(Flag_UserOkay ? 0 : 1);
}
void child_signal(int unused)
{
// might try to pass along the exit status of the child, but that's
// extra work and complication...
exit(0);
}
void emit_help(void)
{
fprintf(stderr, "Usage: waitornot [-U] command [args ..]\n");
exit(EX_USAGE);
}
void reset_term(void)
{
tcsetattr(STDIN_FILENO, TCSAFLUSH, &Original_Termios);
}