4

I just read the Linux scp command issue question and it reminded me that I regularily forget to specify the colon in the host part of a scp command, and thus copying a file locally instead of copying to a remote host, e.g. I do

scp foo host

instead of

scp foo host:

But I never use scp to copy a file locally. So I wonder if there is a way to make scp fail if both (the source and destination) arguments refer to local files.

ashcatch
  • 1,168
  • 11
  • 11

3 Answers3

3

You could write a wrapper function in Bash and name it scp that uses getopts to process the options into a new array leaving the source and destination in the positional parameters, then check those for the presence of "@" and ":" and, if absent, issue an error message. If they're present, then call the real scp using something like command scp "${args[@]}" "$source" "$dest". The command command causes Bash to call the program that's found in the PATH instead of the function by the same name.

Since, at least on my system, scp only supports the short option type, getopts (which also only supports short options) will work fine for this.

The function would look something like this (untested):

scp () {
    # wrapper function to prevent scp of local-only files
    options=":346BCpqrTvc:F:i:J:l:o:P:S:"
    while getopts $options option
    do
        case $option in
            3 | 4 | 6 | B | C | p | q | r | T | v)
                args+=($option)
                ;;
            c | F | i | J | l | o | P | S)
                args+=($option "$OPTARG")
                ;;
            \? ) echo "Unknown option: -$OPTARG" >&2; return 1;;
            :  ) echo "Missing option argument for -$OPTARG" >&2; return 1;;
            *  ) echo "Unimplemented option: -$option" >&2; return 1;;
        esac
    done

    shift $((OPTIND - 1))

    if [[ $1 != *@*:* && $2 != *@*:* ]]
    then
        echo "Local-only copy not permitted"
        echo "to override, use 'command scp ARGS'"
        return 1
    fi
    command scp "${args[@]}" "$1" "$2"
}

The options in this script reflect those accepted by scp at the time of this writing, but they may change over time. That is one of the risks presented by wrapper scripts such as this.

Colin 't Hart
  • 594
  • 4
  • 16
Dennis Williamson
  • 106,229
  • 19
  • 167
  • 187
  • 2
    Nice trick. However, you should consider naming your wrapper something else than "scp" (maybe "scop"). Overloading well-known commands can be a source of tricky problems because most people/scripts will expect the standard functionality. – sleske May 17 '10 at 08:55
  • 2
    Scripts won't use the overloaded function unless you export the function, for example `scp () { ... }; export scp` – Dennis Williamson May 17 '10 at 10:49
  • 1
    Oh, @sleske, in addition to the export information in my previous comment, this type of thing is the purpose of the `command` and `builtin` commands rather than being just a nice trick. – Dennis Williamson May 17 '10 at 11:06
  • Interesting info about "command" and "builtin", didn't know that. Still, I'd be wary of overloading scp, if just for the "principle of least surprise". Well, maybe a matter of taste... – sleske May 17 '10 at 15:16
  • There's a missing closing double quote at the end of the `options=` line. I would normally submit an edit but the stupid software requires at least 6 characters to change... – Colin 't Hart Oct 17 '22 at 13:37
  • @Colin'tHart: Thanks! I fixed it. It stood for over 12 years! – Dennis Williamson Oct 17 '22 at 14:01
  • @DennisWilliamson And scp these days has different arguments... so maybe I should submit an edit anyway... – Colin 't Hart Oct 17 '22 at 15:58
  • @Colin'tHart: Thanks. I'll update it and add a note that options change over time. – Dennis Williamson Oct 17 '22 at 18:41
  • @Colin'tHart: Why remove the exits? – Dennis Williamson Oct 17 '22 at 20:36
  • 1
    @DennisWilliamson Not remove; replace with `return`. As I wrote in the edit comment, `exit` will cause the current shell to exit; you need to use `return` inside functions. See https://stackoverflow.com/a/4419956/391445 – Colin 't Hart Oct 18 '22 at 04:49
1

My scp uses the regular cp to copy local files.

Create a script named cp and make sure scp runs with a modified PATH, so it finds the script instead of the real cp.

The script may be like:

#!/bin/sh
echo 'Local copying unsupported!' >&2
exit 1

Save it as ~/.weird-stuff/cp and make it executable. Then create a wrapper named scp that modifies PATH for the real scp. Example wrapper function:

scp () {
PATH="$HOME/.weird-stuff:$PATH" command scp "$@";
}

You may prefer an equivalent wrapper script.

Disadvantage:

  • There is no guarantee that future versions of scp will rely on cp. If scp stops relying on cp (or on PATH to find cp) then the solution will stop working.

Advantage:

  • The solution (unlike this one) does not need to understand options supported by scp. No matter how scp interprets its command line or if anything in this matter changes in the future, the solution will work, if only scp keeps using cp for local copying (and PATH to find cp).
Kamil Maciorowski
  • 69,815
  • 22
  • 136
  • 202
1

Low cost solution: if you are copying always/often from the same directory, and always/often to the same host, just create a fake file with the name of the host and remove all write permissions on it: touch host && chmod ugo-w host. Then your wrong scp command will fail.

PierU
  • 1,539
  • 5
  • 20