M: I want to add a list of keywords in a text file to the metadata of an image from the Linux command line. Can I?
G: Yes, you can add keywords to an
image’s metadata using exiftool. If you don’t have it
installed, you can install it with:
Fedora:
sudo dnf install perl-Image-ExifToolUsage:
Assuming your keywords are in a text file (keywords.txt),
you can add them to an image’s metadata like this:
exiftool -Keywords+="$(tr '\n' ',' < keywords.txt)" image.jpgThis reads the keywords from the text file, replacing newlines with commas to format them correctly. If your keywords are space-separated instead of newline-separated, you can adjust accordingly.
To verify the changes:
exiftool image.jpg | grep KeywordsLet me know if you need a different metadata tag or format.
M: I’m using xnview. If I’m looking to organize in that program, should I be using keywords, or some other structure?
G: In XnView, Keywords (stored in IPTC or XMP metadata) are a good choice for organizing images if you want a tag-based system that works across different software. However, XnView also supports:
If you want portability (i.e., the tags remain if you move files or use another program), use Keywords via IPTC/XMP. If you’re only organizing within XnView, Categories might be more flexible.
Do you need these keywords for searching, filtering, or something else?
M: Filtering, I think. Is what you described for iptc keywords, or should I alter the methodology?
G: For filtering in XnView, IPTC/XMP Keywords are the best choice if you want the tags to be embedded in the image and work across different programs. The method I gave works for IPTC Keywords, but you might want to adjust it slightly to ensure compatibility with XnView:
Instead of -Keywords, use:
exiftool -IPTC:Keywords+="$(tr '\n' ',' < keywords.txt)" image.jpgThis explicitly writes to the IPTC Keywords field. If you prefer XMP (which is more modern and widely supported), use:
exiftool -XMP:Subject+="$(tr '\n' ',' < keywords.txt)" image.jpgXnView can filter by Keywords in the
Categories Filter panel if you enable metadata reading
(Tools > Settings > Metadata > Import XMP/IPTC Categories).
If you're strictly using XnView and don’t need the keywords embedded in the image, you can assign Categories instead. These are stored in XnView’s database and allow fast filtering but won’t transfer if you move the files outside XnView.
Would you rather have embedded metadata for long-term use, or just use XnView’s internal system?
M: Long term
G: Then IPTC or XMP Keywords are the way to go. Use:
If you want both for maximum compatibility:
exiftool -IPTC:Keywords+="$(tr '\n' ',' < keywords.txt)" -XMP:Subject+="$(tr '\n' ',' < keywords.txt)" image.jpgThis ensures XnView, Adobe apps, and other metadata-aware programs recognize the keywords.
After adding keywords, make sure XnView reads them:
Tools > Settings > Metadata > Import XMP/IPTC Categories.Tools > Settings > Catalog > Rebuild Thumbnails and Metadata.This lets you filter by keywords in XnView while keeping metadata embedded in the image.
M: How about a quick command to determine if a file is an image/video?
G: You can use the
file command:
file --mime-type filenameExample output:
image.jpg: image/jpeg
video.mp4: video/mp4
document.pdf: application/pdf
For batch processing (e.g., checking multiple files in a directory):
file --mime-type * | grep -E 'image|video'This filters the output to show only images and videos. Let me know if you need a more specific check.
M: The context is a loop that says for j in *; do if this is an image/video; then do_stuff; fi; done
G: You can use this loop to check
if a file is an image or video before executing
do_stuff:
for j in *; do
if file --mime-type "$j" | grep -qE 'image|video'; then
do_stuff "$j"
fi
doneThis checks each file's MIME type and runs do_stuff only
if it’s an image or video. Let me know if you need refinements.
M: One issue: the xmp keywords are being read as one single category with commas.
G: That happens because XnView (and some other programs) expect multiple keywords to be stored as separate entries rather than a single comma-separated string. You need to pass the keywords as individual arguments rather than joining them into one string.
Modify your command like this:
exiftool -XMP:Subject+=@keywords.txt image.jpgThis tells exiftool to read the file line by line and
treat each line as a separate keyword. It avoids merging them into one
long string.
For IPTC keywords as well:
exiftool -IPTC:Keywords+=@keywords.txt -XMP:Subject+=@keywords.txt image.jpgThis ensures proper keyword separation in XnView and other software. Try it and check if XnView reads them correctly.
M: Okay, I tried expanding that command to a command line loop; when I used
exiftool -XMP:Subject+=@$j.txt image.jpg
It didn’t add the text file – it added the literal string @jcontents. Did I mess up the syntax?
G: Yes, the issue is that
@$j.txt is being interpreted literally because
exiftool treats @filename as a special syntax
for reading from a file, and it doesn’t expand shell variables inside
it.
To work around this, you need to construct the argument properly using a temporary variable:
for j in *; do
if [[ -f "$j.txt" ]]; then # Ensure the text file exists
exiftool -XMP:Subject+=@"$j.txt" "$j"
fi
doneKey Fixes:
$j.txt: Ensures the filename is
expanded properly.$j.txt exists before running
exiftool, preventing errors."$j" at the end to correctly
apply metadata to the corresponding image.This will apply the keywords from filename.txt to
filename (assuming the image and text file match). Let me
know if you need refinements.
M: The funny thing is, I thought that would be the case, but decided to check. The exiftool created backups named things like filename.jpg_original. Can I get a quick mv command to switch it back to .jpg?
G: Yeah, exiftool
automatically creates backups unless you disable it. To rename them
back, use:
for f in *_original; do
mv -- "$f" "${f%_original}"
doneThis removes _original from all affected files.
For future use, you can disable backups by adding
-overwrite_original to your exiftool
command:
exiftool -overwrite_original -XMP:Subject+=@"$j.txt" "$j"That prevents _original files from being created.
M: Thanks, but at the moment it’s been useful in backtracking from minor setbacks
G: Got it. If you ever want a selective rollback, you can use:
mv filename.jpg_original filename.jpgor restore everything at once:
for f in *_original; do
mv -- "$f" "${f%_original}"
doneLet me know if you need refinements.
M: need some xnview help, please go online to verify your answer… i’ve poked at a bunch of stuff in the past, but need a final version to copy/paste/refer to in the future in terms of settings, especially if i have to switch computers. i want the ‘categories’ part of the program to keep track of the tags that i apply, so i can export them as xml/impt (or whatever those abbreviations are), but NOT to automatically try and add categories from stuff i’m browsing through for the first time. what combination of settings is appropriate for that?
G: You’re basically trying to split XnView into two roles:
That’s doable—but the trick is decoupling import from export. XnView ties them together by default, which is why it keeps “learning” junk categories from files you open.
Tools → Settings → Metadata → Export
👉 This makes Categories → actual embedded keywords
This is the part you want.
Tools → Settings → Metadata → Import
👉 This is the setting that’s auto-creating categories from files you browse. citeturn0search6
When you do want to sync from files → database:
This reads keywords only when you ask for it, instead of constantly. citeturn0search6
XnView has two separate systems:
👉 You want export ON, import OFF
“Categories” in XnView are basically just a UI for managing keywords. citeturn0search1
So:
If you did want hybrid behavior:
This works, but it’s messy and defeats your goal of a clean taxonomy.
If you see:
View → Update catalog from filesThis setup gives you exactly what you described:
And it’s stable across machines once replicated.