Why is the approach not working as wanted?
From the POSIX standard [emphasis mine]:
If the
-foption is not specified, and either the permissions of file do not permit writing and the standard input is a terminal or the-ioption is specified,rmshall write a prompt to standard error and read a line from the standard input. If the response is not affirmative, rm shall do nothing more with the current file and go on to any remaining files.
The condition "the permissions of file do not permit writing" is in conjunction with "the standard input is a terminal". In yes n | rm … the standard input of rm is a pipe, not a terminal,
How to make it work
There are ways to pass input (like lines of n) to a program via its stdin that actually is a new terminal created deliberately to fool the program. E.g. we can write an expect script that runs rm and sends n (plus Enter) after any prompt containing ?.
A basic expect script:
#!/usr/bin/expect --set timeout -1spawn -noecho rm {*}$argvexpect {"\\?" { send "n\r" exp_continue } eof { lassign [wait] pid spawnid os_error_flag value exit $value }}Save it as rmw and make it executable. Now instead of rm t/alpha t/beta t/gamma, run
/path/to/rmw t/alpha t/beta t/gammaWe put the script inside a regular file because we needed $argv. Embedding the script inside a shell script would be easy if expect -c supported $argv; but it does not. The following trick with /dev/stdin shall work and allow you to embed the code as a shell function:
rmw () {expect /dev/stdin "$@" <<'EOF'set timeout -1spawn -noecho rm {*}$argvexpect {"\\?" { send "n\r" exp_continue } eof { lassign [wait] pid spawnid os_error_flag value exit $value }}EOF}Now you don't need a separate regular file and the command may be:
rmw t/alpha t/beta t/gammaNotes:
rmwpasses all its arguments torm. It is possible to pass options (e.g.-r), not only operands.- The script exits with the exit value of
rm.
Simpler approach
When there are just few non-directories to remove, for each file you can test the permissions and conditionally run a separate rm. For many non-directories such loop will probably be significantly slower than a single rm, still it should work. For directories (think rm -r) this approach will not work by itself.
A basic function that does it:
rmw () ( for f; do test -w "$f" && rm -- "$f" done)Now instead of rm t/alpha t/beta t/gamma, run
rmw t/alpha t/beta t/gammaNotes:
- If permissions of some file change after the file undergoes
testand beforermchecks them (and if other conditions are met) then it may happenrmwill still ask interactively. Decide if you wantrm -f. - All arguments of
rmwwill be treated as pathnames. - There is no attempt to compute a meaningful exit value from exit values of multiple
rms or whatever.
"Emulating" rm -r
The approach with test -w can be used even if you need rm -r. POSIX-ly:
find path/to/dir path/to/file path/to/whatever -depth \ -type d -exec rmdir -- {} \; \ -o ! -type d -exec test -w {} \; -exec rm -- {} \;Notes:
rmdirwill throw errors for non-empty directories; ignore them. Portable ways of checking if a directory is empty are rather inelegant, I decided not to obfuscate the code.- Some resources (including the manual of GNU
find) call-exec"action", while e.g.-typeis called "test". In fact "actions" return true or false just like "tests", they are tests (albeit sometimes trivial). This is an example of using-execas a test: the first-execruns an external command (test -w …) and only if it succeeds the next-execwill be evaluated. - If permissions of some file change after the file undergoes
testand beforermchecks them (and if other conditions are met) then it may happenrmwill still ask interactively. Decide if you wantrm -f. - The exit value from
findwill have nothing to do with exit value of anyrm.
With GNU find and its non-portable tests we can do better:
find path/to/dir path/to/file path/to/whatever -depth \ -type d -empty -delete \ -o ! -type d -writable -delete