diff --git a/CHANGELOG.md b/CHANGELOG.md index 822252c49..f644eae1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # fish 2.6.0 (released ???) +## Notable fixes and improvements + +- Jobs running in the background can now be removed from the list of jobs with the new `disown` builtin, which behaves like the same command in other shells (#2810). + +## Other significant changes + - The `export` and `setenv` commands now supports colon-separated `PATH`, `CDPATH` and `MANPATH`. - The `read` command now has a default limit of 10 MiB. If a line is longer than that it will fail with $status set to 122 and the var will be empty. You can set a different limit by setting the FISH_READ_BYTE_LIMIT variable. - `read` now supports the `--silent` flag to hide the characters typed (#838). diff --git a/doc_src/disown.txt b/doc_src/disown.txt new file mode 100644 index 000000000..a8ce16236 --- /dev/null +++ b/doc_src/disown.txt @@ -0,0 +1,26 @@ +\section disown disown - remove a process from the list of jobs + +\subsection disown-synopsis Synopsis +\fish{synopsis} +disown [ PID ... ] +\endfish + +\subsection disown-description Description + +`disown` removes the specified job from the list of jobs. The job itself continues to exist, but fish does not keep track of it any longer. + +Jobs in the list of jobs are sent a hang-up signal when fish terminates, which usually causes the job to terminate; `disown` allows these processes to continue regardless. + +If no process is specified, the most recently-used job is removed (like `bg` and `fg`). If one or more `PID`s are specified, jobs with the specified process IDs are removed from the job list. Invalid jobs are ignored and a warning is printed. + +If a job is stopped, it is sent a signal to continue running, and a warning is printed. It is not possible to use the `bg` builtin to continue a job once it has been disowned. + +The PID of the desired process is usually found by using process expansion, which can specify jobs or search by process name. + +`disown` returns 0 if all specified jobs were disowned successfully, and 1 if any problems were encountered. + +\subsection disown-example Example + +`firefox &; disown` will start the Firefox web browser in the background and remove it from the job list, meaning it will not be closed when the fish process is closed. + +`disown (jobs -p)` removes all jobs from the job list without terminating them. diff --git a/src/builtin.cpp b/src/builtin.cpp index e3815ba2f..92d6afdcf 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -3058,6 +3058,86 @@ static int builtin_bg(parser_t &parser, io_streams_t &streams, wchar_t **argv) { return res; } +/// Helper for builtin_disown +static int disown_job(parser_t &parser, io_streams_t &streams, job_t *j) { + if (j == 0) { + streams.err.append_format(_(L"%ls: Unknown job '%ls'\n"), L"bg"); + builtin_print_help(parser, streams, L"disown", streams.err); + return STATUS_BUILTIN_ERROR; + } + + // Stopped disowned jobs must be manually signalled; explain how to do so + if (job_is_stopped(j)) { + killpg(j->pgid, SIGCONT); + streams.err.append_format( + _(L"%ls: job %d ('%ls') was stopped and has been signalled to continue.\n"), + L"disown", j->job_id, j->command_wcstr()); + } + + if (parser.job_remove(j)) { + return STATUS_BUILTIN_OK; + } else { + return STATUS_BUILTIN_ERROR; + } +} + +/// Builtin for removing jobs from the job list +static int builtin_disown(parser_t &parser, io_streams_t &streams, wchar_t **argv) { + int res = STATUS_BUILTIN_OK; + + if (argv[1] == 0) { + job_t *j; + // Select last constructed job (ie first job in the job queue) that is possible to disown. + // Stopped jobs can be disowned (they will be continued). + // Foreground jobs can be disowned. + // Even jobs that aren't under job control can be disowned! + job_iterator_t jobs; + while ((j = jobs.next())) { + if (j->get_flag(JOB_CONSTRUCTED) && (!job_is_completed(j))) { + break; + } + } + + if (j) { + res = disown_job(parser, streams, j); + } else { + streams.err.append_format(_(L"%ls: There are no suitable jobs\n"), argv[0]); + res = STATUS_BUILTIN_ERROR; + } + } else { + std::set jobs; + + // If one argument is not a valid pid (i.e. integer >= 0), fail without disowning anything, + // but still print errors for all of them. + // Non-existent jobs aren't an error, but information about them is useful. + // Multiple PIDs may refer to the same job; include the job only once by using a set. + for (int i = 1; argv[i]; i++) { + int pid = fish_wcstoi(argv[i]); + if (errno || pid < 0) { + streams.err.append_format(_(L"%ls: '%ls' is not a valid job specifier\n"), argv[0], + argv[i]); + res = STATUS_BUILTIN_ERROR; + } else { + if (job_t *j = parser.job_get_from_pid(pid)) { + jobs.insert(j); + } else { + streams.err.append_format(_(L"%ls: Could not find job '%d'\n"), argv[0], pid); + } + } + } + if (res == STATUS_BUILTIN_ERROR) { + return res; + } + + // Disown all target jobs + for (auto j : jobs) { + res |= disown_job(parser, streams, j); + } + } + + return res; +} + /// This function handles both the 'continue' and the 'break' builtins that are used for loop /// control. static int builtin_break_continue(parser_t &parser, io_streams_t &streams, wchar_t **argv) { @@ -3542,6 +3622,7 @@ static const builtin_data_t builtin_datas[] = { {L"continue", &builtin_break_continue, N_(L"Skip the rest of the current lap of the innermost loop")}, {L"count", &builtin_count, N_(L"Count the number of arguments")}, + {L"disown", &builtin_disown, N_(L"Remove job from job list")}, {L"echo", &builtin_echo, N_(L"Print arguments")}, {L"else", &builtin_generic, N_(L"Evaluate block if condition is false")}, {L"emit", &builtin_emit, N_(L"Emit an event")}, diff --git a/tests/jobs.err b/tests/jobs.err index 5a1052d09..264bfb805 100644 --- a/tests/jobs.err +++ b/tests/jobs.err @@ -1,3 +1,4 @@ bg: '-23' is not a valid job specifier fg: No suitable job: 3 bg: Could not find job '3' +disown: 'foo' is not a valid job specifier diff --git a/tests/jobs.in b/tests/jobs.in index e41b64537..0ddf591dd 100644 --- a/tests/jobs.in +++ b/tests/jobs.in @@ -4,4 +4,9 @@ jobs -c bg -23 1 fg 3 bg 3 +sleep 1 & +disown +jobs -c +disown foo +disown (jobs -p) or exit 0 diff --git a/tests/jobs.out b/tests/jobs.out index 702f017fa..929263874 100644 --- a/tests/jobs.out +++ b/tests/jobs.out @@ -1,3 +1,6 @@ Command sleep sleep +Command +sleep +sleep