Fully translated (but some work to do about urls)
This commit is contained in:
@ -1,22 +1,22 @@
|
||||
---
|
||||
navigation: true
|
||||
title: Scripts bash
|
||||
title: Bash Scripts
|
||||
main:
|
||||
fluid: false
|
||||
---
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
# Scripts bash
|
||||
# Bash Scripts
|
||||
|
||||
Quelques scripts en vracs qui m'ont sauvé la vie.
|
||||
A few random scripts that saved my life.
|
||||
|
||||
## Detection de doublons et remplacement par des hardlinks
|
||||
## Detecting Duplicates and Replacing Them with Hardlinks
|
||||
---
|
||||
|
||||
Six mois après avoir téléchargé des térabytes de media, je me suis rendu compte que Sonarr et Radarr les copaient dans ma biblio Plex au lieu de créer des hardlinks. C'est dû à un mécanisme contre intuitif qui est que si vous montez plusieurs dossiers dans Sonarr/Radarr, il les voit comme deux systemes de fichiers différents. Et ne peut donc pas créer de hardlinks. C'est pour cela qu'il ne faut monter qu'un seul dossier parent, qui contient tous les enfants (`downloads`, `movies`, `tvseries` dans le dossier parent `media` par exemple).
|
||||
Six months after downloading terabytes of media, I realized that Sonarr and Radarr were copying them into my Plex library instead of creating hardlinks. This happens due to a counterintuitive mechanism: if you mount multiple folders in Sonarr/Radarr, it sees them as different filesystems and thus cannot create hardlinks. That’s why you should mount only one parent folder containing all child folders (like `downloads`, `movies`, `tvseries` inside a `media` parent folder).
|
||||
|
||||
J'ai donc restructuré mes dossiers, remis à la main chaque chemin dans Qbittorrent, Plex, et autres. Il restait à trouver un moyen de détecter les doublons existants et d'automatiquement les supprimer et de créer des hardlinks à la place, pour économiser de l'espace.
|
||||
So I restructured my directories, manually updated every path in Qbittorrent, Plex, and others. The last challenge was finding a way to detect existing duplicates, delete them, and automatically create hardlinks instead—to save space.
|
||||
|
||||
Mes dossiers :
|
||||
My directory structure:
|
||||
|
||||
```console
|
||||
.
|
||||
@ -28,84 +28,85 @@ Mes dossiers :
|
||||
└── tvseries
|
||||
```
|
||||
|
||||
Mes dossiers originaux sont dans `seedbox`, et il ne faut surtout pas les modifier pour qu'ils continuent d'etre "seed". Les copies, et donc doublons, sont dans `movies` et `tvseries`. Mais pour complexifier la chose, j'ai aussi des media uniques originaux déposés par ailleurs dans `movies` et `tvseries`, sinon cela serait trop facile. Et dans ces deux dossiers, il peut y avoir des sous dossiers, des sous-sous dossiers, etc.
|
||||
The originals are in `seedbox` and must not be modified to keep seeding. The copies (duplicates) are in `movies` and `tvseries`. To complicate things, there are also unique originals in `movies` and `tvseries`. And within those, there can be subfolders, sub-subfolders, etc.
|
||||
|
||||
L'idée est donc de :
|
||||
So the idea is to:
|
||||
|
||||
- lister les originaux dans seedbox
|
||||
- lister les fichiers dans movies
|
||||
- comparer les deux listes et isoler les chemins des doublons
|
||||
- supprimer les doublons
|
||||
- hardlinker les originaux dans les dossiers des doublons supprimés
|
||||
- list the originals in seedbox
|
||||
- list files in movies and tvseries
|
||||
- compare both lists and isolate duplicates
|
||||
- delete the duplicates
|
||||
- hardlink the originals to the deleted duplicate paths
|
||||
|
||||
Alors oui j'ai demandé à ChatGPT et à Qwen3 (que j'héberge sur une machine dédiée à l'IA). Et evidemment ils m'ont conseillé les rfind, rdfind, dupes, rdupes, rmlint... Mais comparer les hash de 30TB de media, faudrait plusieurs jours, j'ai vite abandonné.
|
||||
Yes, I asked ChatGPT and Qwen3 (which I host on a dedicated AI machine). Naturally, they suggested tools like rfind, rdfind, dupes, rdupes, rmlint... But hashing 30TB of media would take days, so I gave up quickly.
|
||||
|
||||
Au final, je n'ai que des `.mkv` à chercher et les doublons ont exactement les mêmes noms que les originaux, ce qui simplifie grandement la tâche. Un simple script bash devait donc être suffisant.
|
||||
In the end, I only needed to find `.mkv` files, and duplicates have the exact same name as the originals, which simplifies things a lot. A simple Bash script would do the job.
|
||||
|
||||
Je vous passe les incessantes questions réponses avec ChatGPT, je suis assez déçu. Qwen3 a été bien plus propre. ChatGPT n'a pas cessé de mettre des solutions type awk, qui pètent la lecture des chemins au moindre espace. En faisant relire à Qwen, et en lui demandant de se passer de awk, le résultat a été immediatement plus qualitatif.
|
||||
Spare you the endless Q&A with ChatGPT—I was disappointed. Qwen3 was much cleaner. ChatGPT kept pushing awk-based solutions, which fail on paths with spaces. With Qwen’s help and dropping awk, the results improved significantly.
|
||||
|
||||
Pour tester, j'ai d'abord demandé un script qui ne fait que lister et comparer :
|
||||
To test, I first asked for a script that only lists and compares:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Créer un tableau associatif pour stocker les doublons
|
||||
# Create an associative array to store duplicates
|
||||
declare -A seen
|
||||
|
||||
# Trouver tous les fichiers .mkv uniquement (exclure les dossiers)
|
||||
# Find all .mkv files only (exclude directories)
|
||||
find /media/seedbox /media/movies /media/tvseries -type f -name "*.mkv" -print0 | \
|
||||
while IFS= read -r -d '' file; do
|
||||
# Obtenir l'inode du fichier et son chemin
|
||||
# Get the file's inode and name
|
||||
inode=$(stat --format="%i" "$file")
|
||||
filename=$(basename "$file")
|
||||
|
||||
# Si ce nom de fichier a déjà été vu
|
||||
# If the filename has been seen before
|
||||
if [[ -n "${seen[$filename]}" ]]; then
|
||||
# Vérifier si l'inode est différent du précédent
|
||||
# Check if the inode is different from the previous one
|
||||
if [[ "${seen[$filename]}" != "$inode" ]]; then
|
||||
# Ajouter le doublon à la sortie en affichant les chemins complets
|
||||
echo "Doublons pour \"$filename\" :"
|
||||
# Output the duplicates with full paths
|
||||
echo "Duplicates for \"$filename\":"
|
||||
echo "${seen["$filename"]} ${seen["$filename:full_path"]}"
|
||||
echo "$inode $file"
|
||||
echo
|
||||
fi
|
||||
else
|
||||
# Si c'est la première fois qu'on rencontre ce nom de fichier
|
||||
seen[$filename]="$inode"
|
||||
seen["$filename:full_path"]="$file"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
J'ai ainsi obtenu ce type de réponse :
|
||||
This gave me outputs like:
|
||||
|
||||
```
|
||||
Doublons pour "episode1.mkv" :
|
||||
Duplicates for "episode1.mkv":
|
||||
1234567 /media/seedbox/sonarr/Serie 1/Season1/episode1.mkv
|
||||
2345678 /media/tvseries/Serie 1/Season1/episode1.mkv
|
||||
```
|
||||
|
||||
Avec "awk", il se serait arrêté à `/media/seedbox/sonarr/Serie`. Je ne suis absolument pas un pro, mais Qwen3 a été plus performant et m'a expliqué de A à Z pourquoi et comment faire.
|
||||
With `awk`, it would’ve stopped at `/media/seedbox/sonarr/Serie`. I’m far from an expert, but Qwen3 performed better and explained everything clearly.
|
||||
|
||||
Une fois que j'ai vu que cela fonctionnait bien, j'ai demandé un script qui fait l'intégralité de la cinématique, de la comparaison aux hardlinks en passant par la suppression des doublons.
|
||||
Encore une fois ChatGPT a été décevant. Malgré mes demandes, il créait d'abord les hardlinks et ensuite il supprimait les doublons. Ce qui.. suprimme aussi le lien (meme si cela conserve l'originale). Idiot.
|
||||
Petit détour par Qwen3, et ma RTX 5090 en PLS, et paf un résultat bien plus propre. Bon il a gardé les emoji de ChatGPT qui peut pas s'empecher d'en mettre partout, mais voilà :
|
||||
Once I verified the output, I asked for a complete script: compare, delete duplicates, create hardlinks.
|
||||
|
||||
Again, ChatGPT disappointed. Despite my requests, it created hardlinks *before* deleting the duplicates—effectively linking and then deleting the link (though the original is kept). Not helpful.
|
||||
|
||||
Quick stopover to Qwen3, RTX 5090 in overdrive, and bam—much better result. Yes, it kept ChatGPT-style emojis, but here it is:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
echo "🔍 Étape 1 : Indexation des fichiers originaux dans /media/seedbox..."
|
||||
echo "🔍 Step 1: Indexing original files in /media/seedbox..."
|
||||
declare -A seen
|
||||
|
||||
# Indexe tous les .mkv dans seedbox
|
||||
# Index all .mkv files in seedbox
|
||||
while IFS= read -r -d '' file; do
|
||||
filename=$(basename "$file")
|
||||
seen["$filename"]="$file"
|
||||
done < <(find /media/seedbox -type f -name "*.mkv" -print0)
|
||||
|
||||
echo "📦 Étape 2 : Remplacement automatique des doublons..."
|
||||
echo "📦 Step 2: Automatically replacing duplicates..."
|
||||
total_doublons=0
|
||||
total_ko_economises=0
|
||||
total_ko_saved=0
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
filename=$(basename "$file")
|
||||
@ -117,30 +118,30 @@ while IFS= read -r -d '' file; do
|
||||
|
||||
if [[ "$inode_orig" != "$inode_dupe" ]]; then
|
||||
size_kb=$(du -k "$file" | cut -f1)
|
||||
echo "🔁 Remplacement :"
|
||||
echo " Doublon : $file"
|
||||
echo "🔁 Replacing:"
|
||||
echo " Duplicate : $file"
|
||||
echo " Original : $original"
|
||||
echo " Taille : ${size_kb} Ko"
|
||||
echo " Size : ${size_kb} KB"
|
||||
|
||||
rm "$file" && ln "$original" "$file" && echo "✅ Hardlink créé."
|
||||
rm "$file" && ln "$original" "$file" && echo "✅ Hardlink created."
|
||||
|
||||
total_doublons=$((total_doublons + 1))
|
||||
total_ko_economises=$((total_ko_economises + size_kb))
|
||||
total_ko_saved=$((total_ko_saved + size_kb))
|
||||
fi
|
||||
fi
|
||||
done < <(find /media/movies /media/tvseries -type f -name "*.mkv" -print0)
|
||||
|
||||
echo ""
|
||||
echo "🧾 Résumé :"
|
||||
echo " 🔗 Doublons remplacés par hardlink : $total_doublons"
|
||||
echo " 💾 Espace disque économisé approximatif : ${total_ko_economises} Ko (~$((total_ko_economises / 1024)) Mo)"
|
||||
echo "✅ Terminé."
|
||||
echo "🧾 Summary:"
|
||||
echo " 🔗 Duplicates replaced by hardlink: $total_doublons"
|
||||
echo " 💾 Approx. disk space saved: ${total_ko_saved} KB (~$((total_ko_saved / 1024)) MB)"
|
||||
echo "✅ Done."
|
||||
```
|
||||
|
||||
Bilan j'ai :
|
||||
- appris pas mal de subtilité bash
|
||||
- appris qu'il ne faut jamais copier coller un script généré ChatGPT sans le comprendre et sans le tester en dry-run
|
||||
- appris que Qwen sur une RTX 5090 est plus cohérent que ChatGPT 4o sur des fermes de serveurs (je vous passe les résultats de la version "normale").
|
||||
- appris que même quand on a 100TB d'espace, monitorer ce dernier m'aurait permis de voir beaucoup plus tot que j'avais 12TB de doublons qui trainent.
|
||||
So, in conclusion, I:
|
||||
- Learned many Bash subtleties
|
||||
- Learned never to blindly copy-paste a ChatGPT script without understanding and dry-running it
|
||||
- Learned that Qwen on a RTX 5090 is more coherent than ChatGPT-4o on server farms (not even mentioning “normal” ChatGPT)
|
||||
- Learned that even with 100TB of storage, monitoring it would’ve alerted me much earlier to the 12TB of duplicates lying around
|
||||
|
||||
A plus tard pour de nouvelles aventures passionnantes.
|
||||
Catch you next time for more exciting adventures.
|
||||
|
Reference in New Issue
Block a user