mastodon.social is one of the many independent Mastodon servers you can use to participate in the fediverse.
The original server operated by the Mastodon gGmbH non-profit

Administered by:

Server stats:

348K
active users

mcc

Is there a way to do `mv`, in either linux or Perl, that will fail if I try to copy a file over another file?

In the mv manpage, I find a `-n` or `--no-clobber` option which does indeed refuse to replace file A with file B. But it is a silent failure. $? is 0 after I run an unsuccessful `mv -n`.

@madrush That's an interesting question.

@mcc

if [ -e dstfile ]; then echo "nope" else mv srcfile dstfile fi

@mcc you could add an -n to the mv to protect against a race condition too if you wanted

@mcc you could also do something similar in perl i think:

perl -e 'rename("srcfile", "destfile") unless -e "dstfile";'

@d6 More idiomatically atomic is:

mv -n ___ || echo "nope" >&2

But somehow I think this is not new to @mcc..

@akkartik @d6 Wait, what? How does the || work if mv -n returns 0 in all cases?

@mcc @d6 Ugh, I just tried it. You said $? is 0. That's quite unfortunate.

@akkartik yeah i think the point @mcc was making is that -n doesn't alter the exit code

@d6 @mcc I like this one (with -n because why not). Alternatively,

mv -n srcfile dstfile && if [ -e srcfile ]; then echo “nope” fi

@jlundell @d6 @mcc I like this one, you are just checking for success like it did use error codes the way you wanted, less toctou, maybe none depending on how mv --no-clobber is implemented. I wonder if --verbose prints anything different on failure than success as well that could be acted on.

@mcc you could prob use File::Copy module for this in Perl

@mcc

use File::Copy;

my $source = "doot.txt";
my $destination = "/path/to/doot.txt";

if (-e $destination) {
die "Destination file already exists. Move operation failed.\n";
}

move($source, $destination) or die "Move operation failed: $!";

@mcc @rooneymcnibnug Well, not quite atomic (something else could create the file between the `-e` check and the `move`). But it's hard to do better -- the underlying `rename` system call is documented to trample an existing directory entry without checking, so the only way to get genuinely atomic behavior is to guard the whole thing with a lock somewhere else.

@rooneymcnibnug In industrial settings, I'd be nervous about this approach because it is not atomic. But I am not in an industrial setting.

@mcc I suspect a scripting solution is the answer here. You'd just need to do a check before the actual operation. You can do this in perl but that might be overkill. In bash, for example:

if [ -f "$FILE" ]; then
echo "Cannot overwrite $FILE"
exit 1
fi

# proceed with move command here

@zalasur @mcc

this would fail (or rather, succeed destructively) with for instance `mv foo bar` where bar is a directory already containing a file named foo

@gutmunchies @mcc Yeah the example wasn't exhaustive. I think -e would be a better flag to use if you're trying to cover all the edge cases.

@roguelazer @mcc obligatory "that doesn't work across filesystems" caveat?

@hisham_hm @roguelazer I also wonder what it does in WSL while operating on a NTFS drive.

@mcc

Update to (at least) coreutils 9.2, or use a distro which provides that.

With that version, mv -n will print an error and return 1 if a file is skipped. (Previously, it just printed nothing to indicate it was doing nothing. Insert Drake meme here.)

@mcc

There is a new `--update none` option to get the old noclobber behavior, if you want it.

tbh I'm surprised 1) they made what could be a breaking change and 2) to my knowledge there has been no "you broke my scripts" outcry. Although probably the second can be found if I look hard enough.

Also, apparently posix only specifies -i and -f. Because, sure.

@mcc

IMHO good for the coreutils maintainers for making some of this stuff make more sense after all of these years.

But I'll wait for the "they hate Unix!" crowd to show up.

@mattdm @mcc Unfortunately `-n` doesn't seem to be race-free though :/

unix.stackexchange.com/questio

With that in mind (if still true), I don't see any worth in a `-n` flag (invoking the "it's not UNIX" meme here :D). Then again, while there are many very bad options added by the GNU project if you ask me (GNU/tar's --checkpoint-action flag springs to mind), but this'd be a really useful flag in principle.

Unix & Linux Stack Exchangemv: Move file only if destination does not existCan I use mv file1 file2 in a way that it only moves file1 to file2 if file2 doesn't exist? I've tried yes n | mv -i file1 file2 (this lets mv ask if file2 should be overridden and automatically

@ljrk @mcc

Haven't tested, but I believe that's been fixed sometime after 2015. From the release notes for 8.30 (2018):

``` 'mv -n A B' no longer suffers from a race condition that can
overwrite a simultaneously-created B. This bug fix requires
platform support for the renameat2 or renameatx_np syscalls, found
in recent Linux and macOS kernels. As a side effect, 'mv -n A A'
now silently does nothing if A exists.
[bug introduced with coreutils-7.1]
```

@ljrk @mcc

Also this explains the `mv -n` silence when I tested on RHEL 7. :)

@ljrk @mcc

Anyway, everything is terrible.

*switches to plan9*

@mattdm @mcc Thanks for researching this. And I agree on all counts :D

@mattdm @mcc Jop, @soller is of course here too, and I'm absolutely stoked about how far RedoxOS has already progressed! Of course, there's always and will always be a lot to do, but the velocity of the project is making me hopeful.

@ljrk @mattdm @soller The one thing that makes me a little worried is the microkernel. Not to say it's a bad idea, just I wonder if it will last. I think of Apple's experience with starting with a microkernel believing the performance difference negligible before eventually realizing when you're writing a kernel your incentives are eventually to move performance encumbrances to absolute zero…

@mcc @mattdm @soller Jup, but I do hope the design is worth it. And in the end... if it's really that bad, just monofy it :'-D

@ljrk @mattdm @soller i mean, "monofy it once you need to" seems to have worked long term for apple, so…

@mcc If they're on the same filesystem so the mv is actually a rename not cp+rm, you can use ln (without -s; hard link) instead. rm the old name only after the ln succeeds.

If it's cross-fs, you can do the same with a temp file on the dest fs.

@mcc

my copy of `mv` has an `-i` (interactive) option which generates a prompt in the event of a potential overwrite

@mcc

On Linux, the renameat2 system call takes a flags argument that can include RENAME_NOREPLACE for the behavior you want.

As far as I can tell, coreutils mv will actually use this flag on first try, but then it catches the error and falls back on a hugely complicated path without it.

I can't think of a way to invoke renameat2 from Perl without hardcoding a lot of constants; I wonder if writing a simple C utility might not be the least-bad solution.

@mcc

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
if (3 != argc)
exit(EXIT_FAILURE);
if (0 != renameat2(AT_FDCWD, argv[1],
AT_FDCWD, argv[2],
RENAME_NOREPLACE))
exit(EXIT_FAILURE);
exit(EXIT_SUCCESS);
}

@mcc

If all else fails, a 10 line bash script to check file exists, move to/use backup file name and or fail otherwise would work.