I noticed this inconsistency between the Julia cp and the command-line cp:
julia> touch("file");
julia> mkdir("dir");
julia> cp("file", joinpath("dir", "."))
ERROR: ArgumentError: 'dir/.' exists. `force=true` is required to remove 'dir/.' before copying.
Stacktrace:
[1] checkfor_mv_cp_cptree(src::String, dst::String, txt::String; force::Bool)
@ Base.Filesystem ./file.jl:308
[2] cp(src::String, dst::String; force::Bool, follow_symlinks::Bool)
@ Base.Filesystem ./file.jl:345
[3] cp(src::String, dst::String)
@ Base.Filesystem ./file.jl:345
[4] top-level scope
@ REPL[3]:1
To edit a specific method, type the corresponding number into the REPL and press Ctrl+Q
shell> cp file dir/.
julia> isfile(joinpath("dir", "file"))
true
Additionally, when using . with force=true Julia attempts to delete the directory instead of the file:
julia> cp("file", joinpath("dir", "."), force=true)
ERROR: SystemError (with dir/.): rmdir: Invalid argument
Stacktrace:
[1] systemerror(p::Symbol, errno::Int32; extrainfo::String)
@ Base ./error.jl:168
[2] #systemerror#51
@ ./error.jl:167 [inlined]
[3] rm(path::String; force::Bool, recursive::Bool)
@ Base.Filesystem ./file.jl:286
[4] checkfor_mv_cp_cptree(src::String, dst::String, txt::String; force::Bool)
@ Base.Filesystem ./file.jl:306
[5] cp(src::String, dst::String; force::Bool, follow_symlinks::Bool)
@ Base.Filesystem ./file.jl:345
[6] top-level scope
@ REPL[8]:1
To edit a specific method, type the corresponding number into the REPL and press Ctrl+Q
shell> cp -f file dir/.
I would argue that the fact that the cp command does different things depending on whether the destination is a directory or not is undesirable in a programmatic API. It means that when you write code that does cp(src, dst) you don't know if the end result is that dst is a copy of src or joinpath(dst, basename(src)) is a copy of src. That behavior is fine in a purely interactive tool but bad for programmatic use.
That's a reasonable position. I would say we should probably add a note to the docstring mentioning this difference
Similar but far more destructive:
julia> touch("file")
"file"
julia> mkdir("dir")
"dir"
julia> touch(joinpath("dir", "important-file"))
"dir/important-file"
julia> collect(walkdir("."))
2-element Vector{Any}:
(".", ["dir"], ["file"])
("./dir", String[], ["important-file"])
julia> cp("file", "dir/", force=true)
ERROR: IOError: open: no such file or directory (ENOENT)
Stacktrace:
[1] uv_error
@ ./libuv.jl:97 [inlined]
[2] open(path::String, flags::UInt16, mode::UInt64)
@ Base.Filesystem ./filesystem.jl:87
[3] sendfile(src::String, dst::String)
@ Base.Filesystem ./file.jl:911
[4] cp(src::String, dst::String; force::Bool, follow_symlinks::Bool)
@ Base.Filesystem ./file.jl:351
[5] top-level scope
@ REPL[5]:1
To edit a specific method, type the corresponding number into the REPL and press Ctrl+Q
julia> cp("file", "dir", force=true)
"dir"
julia> collect(walkdir("."))
1-element Vector{Any}:
(".", String[], ["dir", "file"])
I dunno, seems like that's what force=true would do 🤷♂️