プロセスが完了するか、ユーザーがキーを押すまで待ち​​ます。

プロセスが完了するか、ユーザーがキーを押すまで待ち​​ます。

Bashスクリプトの一部を終了するには2つの方法が必要です。

カウンターが事前定義された数値に達するか、ユーザーが手動でスクリプトがカウンターの現在の値を使用し続けるように強制します。

具体的には - 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 -tsleepscannew()

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デバイスを一覧表示するポーリングデーモンです。おそらく許可されます。scannewreadbashtrapreadwhile trueread -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);
}

関連情報