In practise, and despite most of the other answers, essentially the only way a mainstream, real world programming language $L$ is defined, is by building a $L$-compiler that translates to, or an $L$-interpreter that executes in another programming language (typically a machine language), which is assumed to be understood.
The others suggestion (like K, or natural deduction rules) are used for small toy languages, but not for mainstream, real world programming languages, such as C, C++, Haskell, Agda, Lean, Python etc. That's because the effort of fully defining the syntax and semantics of a mainstream PL is so heroic that one doesn't want to do it more than once. And where it is done more than once (e.g. clang and gcc) the results don't coincide ...
The reason why the other methods are used and very useful is essentially divide-and-conquer: we sketch the essence of computation mechanisms in toy calculi (e.g. $\lambda$-calculus for $\beta$-reduction, and $\pi$-calculus for message passing parallelism, or Datalog for proof search, ...), and ignore handling of various other problems (e.g. finiteness of numbers) because that would obscure the phenomenon we are trying to handle, and leave it to another toy model. The full, real-world compiler then 'only' has to solve of brining all those many different issues together in one unified whole. A good example here is ML, where Robin Milner separated out the presentation of type-inference into separate papers (e.g. L. Damas, R. Milner, Principal type-schemes for functional programs) which do not explain type inference for full ML, but only a toy model of it, a small λ-calculus. I think this is a good approach: compiler / interpreter + small calculi to exhibit novel ideas in the language.