bastien-mrq/clip / clip
#!/usr/bin/env bash
#
# clip — copie un ou plusieurs fichiers dans le presse-papier.
# Copyright (C) 2026 Bastien Marques — Licence GPL-3.0-or-later (voir LICENSE)
#
# Par défaut : copie le FICHIER lui-même (collable en pièce jointe avec Ctrl+V
# dans Google Chat, Gmail, Slack, un gestionnaire de fichiers, etc.)
# Compatible macOS (osascript/pbcopy) et Linux (wl-copy / xclip / xsel).
set -euo pipefail
usage() {
cat <<'EOF'
Usage : clip [OPTIONS] [FICHIER...]
Copie un ou plusieurs fichiers dans le presse-papier (pièce jointe par défaut).
Fonctionne avec tout type de fichier : PDF, image, zip, vidéo, dossier...
Options :
(défaut) Copie le(s) fichier(s) eux-mêmes → Ctrl+V les colle en
pièce jointe dans Google Chat, Gmail, Slack, Fichiers...
-c, --content Copie le contenu texte du fichier
-p, --path Copie le(s) chemin(s) absolu(s)
-h, --help Affiche cette aide
Exemples :
clip rapport.pdf Ctrl+V → le PDF part en pièce jointe
clip a.pdf b.zip photo.jpg Copie les trois fichiers d'un coup
clip -c notes.txt Ctrl+V → colle le texte de notes.txt
git diff | clip Copie la sortie d'une commande (texte)
EOF
}
err() { printf 'clip: %s\n' "$1" >&2; exit 1; }
abspath() {
if command -v realpath >/dev/null; then
realpath "$1"
else
cd "$(dirname "$1")" && printf '%s/%s\n' "$(pwd)" "$(basename "$1")"
fi
}
# Encode un chemin en URI file:// valide (espaces, accents, etc.)
# Itère octet par octet (LC_ALL=C) pour encoder correctement l'UTF-8.
urlencode_path() {
local LC_ALL=C s="$1" out="" c i
for ((i = 0; i < ${#s}; i++)); do
c="${s:i:1}"
case "$c" in
[a-zA-Z0-9/._~-]) out+="$c" ;;
*) printf -v c '%%%02X' "'$c"; out+="$c" ;;
esac
done
printf '%s' "$out"
}
# --- Copie TEXTE (contenu, chemin, stdin) --------------------------------
copy_text() {
case "$(uname -s)" in
Darwin) pbcopy ;;
Linux)
if [ -n "${WAYLAND_DISPLAY:-}" ] && command -v wl-copy >/dev/null; then
wl-copy
elif command -v xclip >/dev/null; then
xclip -selection clipboard
elif command -v xsel >/dev/null; then
xsel --clipboard --input
elif command -v wl-copy >/dev/null; then
wl-copy
else
err "aucun outil presse-papier trouvé.
Installez : Wayland → wl-clipboard | X11 → xclip"
fi
;;
*) err "système non pris en charge : $(uname -s)" ;;
esac
}
# --- Copie FICHIER(S) (références, pour coller en pièce jointe) ------------
copy_file_refs() {
case "$(uname -s)" in
Darwin)
# Écrit une référence public.file-url par fichier via AppKit, plus le
# format legacy NSFilenamesPboardType (liste de tous les chemins) que
# Chrome utilise pour un collage multi-fichiers — comme le Finder en ⌘C.
# Le delay final est indispensable : sans lui, le processus quitte avant
# que les items soient transmis au serveur de presse-papiers, et seul
# le premier survit.
osascript -l JavaScript -e '
ObjC.import("AppKit")
function run(argv) {
const pb = $.NSPasteboard.generalPasteboard
pb.clearContents
const items = argv.map(p => {
const item = $.NSPasteboardItem.alloc.init
item.setStringForType($.NSURL.fileURLWithPath(p).absoluteString, "public.file-url")
return item
})
if (!pb.writeObjects($(items))) throw new Error("échec de l’écriture dans le presse-papier")
pb.setPropertyListForType($(argv), "NSFilenamesPboardType")
delay(0.3)
return ""
}' "$@" >/dev/null
;;
Linux)
local uris="" p
for p in "$@"; do
uris+="file://$(urlencode_path "$p")"$'\n'
done
if [ -n "${WAYLAND_DISPLAY:-}" ] && command -v wl-copy >/dev/null; then
printf '%s' "$uris" | wl-copy --type text/uri-list
elif command -v xclip >/dev/null; then
printf '%s' "$uris" | xclip -selection clipboard -t text/uri-list
else
err "il faut wl-copy (Wayland) ou xclip (X11) pour copier un fichier.
Installez : Wayland → sudo apt install wl-clipboard | X11 → sudo apt install xclip
(xsel ne sait pas copier des fichiers, seulement du texte)"
fi
;;
*) err "système non pris en charge : $(uname -s)" ;;
esac
}
# --- Analyse des arguments ------------------------------------------------
mode="file"
files=()
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) usage; exit 0 ;;
-c|--content) mode="content"; shift ;;
-p|--path) mode="path"; shift ;;
--) shift; files+=("$@"); break ;;
-*) err "option inconnue : $1 (voir clip --help)" ;;
*) files+=("$1"); shift ;;
esac
done
# --- Exécution -------------------------------------------------------------
if [ ${#files[@]} -eq 0 ]; then
if [ ! -t 0 ]; then
copy_text
echo "✓ Copié depuis stdin (texte)"
exit 0
fi
usage
exit 1
fi
abs_files=()
for f in "${files[@]}"; do
[ -e "$f" ] || err "fichier introuvable : $f"
abs_files+=("$(abspath "$f")")
done
case "$mode" in
file)
copy_file_refs "${abs_files[@]}"
if [ ${#abs_files[@]} -eq 1 ]; then
echo "✓ Fichier copié : ${abs_files[0]}"
else
echo "✓ ${#abs_files[@]} fichiers copiés :"
printf ' %s\n' "${abs_files[@]}"
fi
echo " → Ctrl+V (⌘V sur Mac) dans Google Chat/Gmail/Slack pour joindre"
;;
content)
[ ${#abs_files[@]} -eq 1 ] || err "-c ne prend qu'un seul fichier"
[ -f "${abs_files[0]}" ] || err "'${files[0]}' n'est pas un fichier régulier"
copy_text < "${abs_files[0]}"
echo "✓ Contenu copié : ${files[0]}"
;;
path)
printf '%s\n' "${abs_files[@]}" | copy_text
if [ ${#abs_files[@]} -eq 1 ]; then
echo "✓ Chemin copié : ${abs_files[0]}"
else
echo "✓ ${#abs_files[@]} chemins copiés"
fi
;;
esac