← All scripts

// Bash scripts

Synonyms in the terminal

Looks up German synonyms straight from the shell — data from OpenThesaurus. No jq, no browser, no tab switching.

TerminalLanguageAPI
Version1.3.0
Updated2026-07-11
Tested onTested on a Arch Linux x86_64
LicenceMIT
Requiresbash 5, curl, coreutils
Size128 lines · 3.9 KB
SHA256 0e0c3dd7ff312043e51aca170bde83c5d479bf1554ef2289b8943d2ae2f809bd Verify after downloading: echo "0e0c3dd7ff312043e51aca170bde83c5d479bf1554ef2289b8943d2ae2f809bd synonyme.sh" | sha256sum -c

Read it first, then run it. The complete source is further down on this page. There is deliberately no curl … | bash one-liner here.

Use at your own risk — no liability for data loss or system damage. See the terms of use.

What the script is for

You are writing, you get stuck on a word, and the search for a better one reliably leads to the browser — and from there into some tab that has nothing to do with the text any more. This script answers the question where you are already typing: in the terminal.

$ synonyme.sh schnell
zügig, rasch, flink, hurtig, geschwind, in Windeseile, im Eiltempo, fix, behände, …

The data comes from OpenThesaurus, a free German thesaurus. The script only reads, changes nothing and needs no account.

What it does

  • Asks OpenThesaurus for synonyms of a word (or a phrase)
  • Prints them comma-separated, wrapped to the width of your terminal
  • --plain prints one hit per line — so the result can be piped onwards (| fzf, | grep, | head)
  • --limit N shortens the list, --raw returns the JSON response untouched
  • The search word itself is dropped from the results, duplicates as well

How it works

It started as a shell function in my .bashrc that tore the XML interface apart with sed and cut. Cleaning it up for publication added three things one happily leaves out of a private function:

Encoding. wget "…?q=$*" breaks as soon as the word contains an umlaut or a space — and German words like doing that. With curl the library handles it:

curl -fsSG --max-time 10 \
     --data-urlencode "q=${QUERY}" \
     --data "format=application/json" \
     "$API"

No jq. The obvious move would be to filter the JSON response with jq — but jq is not installed on a fresh Debian, and a script that demands a package first does not get used a second time. For fields called "term", grep is entirely enough:

grep -o '"term":"[^"]*"' | cut -d'"' -f4 | grep -vixF "$QUERY" | awk '!seen[$0]++'

That awk '!seen[$0]++' is the shortest way to drop duplicates without destroying the order — sort -u would re-sort the hits alphabetically and throw away the ranking OpenThesaurus gives you.

Wrap only in a terminal. The original always wrapped at $(tput cols). In a pipe that is nonsense: there you want lines, not layout. So it only wraps when the output really lands in a terminal:

if [[ -t 1 ]] && command -v tput >/dev/null && COLS=$(tput cols 2>/dev/null); then

A script that looks different depending on whether it writes to a terminal or a pipe is not a bug — it is good Unix behaviour. ls does exactly the same thing.

Why I automated this

It was never an automation project, just a convenience: a function in .bashrc, aliased to syn. The reason to write it up as a script now is a different one — you cannot hand a .bashrc function to anyone. It has no --help, no error handling, and it breaks on the first word with an umlaut. That is exactly the difference between "works on my machine" and "somebody else can use this", and this small script is the shortest example of it I have.

As a shell function it of course still works:

# ~/.bashrc
syn() { /usr/local/bin/synonyme.sh "$@"; }

A privacy note, because it belongs here: the search word is sent over HTTPS to openthesaurus.de. If you do not want that, do not use this script. The data there is licensed CC-BY-SA.

Usage

synonyme.sh Haus
synonyme.sh --limit 5 schnell
synonyme.sh --plain Datei | fzf   # one hit per line

Source

synonyme.sh
#!/usr/bin/env bash
# ============================================================
#  synonyme.sh — deutsche Synonyme im Terminal nachschlagen.
#
#  Fragt OpenThesaurus (openthesaurus.de) und gibt die Treffer
#  als Liste aus. Liest nur, ändert nichts am System.
#
#  Hinweis: Das Suchwort wird an openthesaurus.de übertragen.
#
#  Autor:   Matthias Meister — https://scripts.web.mm-core.de
#  Lizenz:  MIT — Copyright (c) 2026 Matthias Meister
#           Volltext: https://scripts.web.mm-core.de/lizenz/
#  Version: 1.0.0
# ============================================================
set -euo pipefail

VERSION="1.0.0"
API="https://www.openthesaurus.de/synonyme/search"
TIMEOUT=10

LIMIT=0          # 0 = alle
PLAIN=0          # 1 = ein Synonym pro Zeile
RAW=0            # 1 = rohe JSON-Antwort ausgeben

usage() {
    cat <<'EOF'
synonyme.sh — deutsche Synonyme nachschlagen (Quelle: openthesaurus.de).

  Verwendung:
    synonyme.sh WORT [WORT ...]

  Optionen:
    --limit N      Nur die ersten N Treffer
    --plain        Ein Synonym pro Zeile (gut für Pipes und Skripte)
    --raw          Die rohe JSON-Antwort ausgeben
    --version      Version ausgeben
    -h, --help     Diese Hilfe

  Beispiele:
    synonyme.sh Haus
    synonyme.sh --limit 5 schnell
    synonyme.sh --plain Datei | fzf

  Als Shell-Funktion (in ~/.bashrc):
    syn() { /usr/local/bin/synonyme.sh "$@"; }

  Exit-Code:
    0  Treffer gefunden
    1  kein Treffer oder Dienst nicht erreichbar
    2  Aufruffehler

  Datenschutz: Das Suchwort geht per HTTPS an openthesaurus.de.
  Die Daten dort stehen unter CC-BY-SA — bei Weiterverwendung Quelle nennen.
EOF
}

die() { printf 'FEHLER: %s\n' "$1" >&2; exit 1; }

# ---- Argumente ----------------------------------------------
WORDS=()
while [[ $# -gt 0 ]]; do
    case "$1" in
        --limit)    LIMIT="${2:?--limit braucht eine Zahl}"; shift ;;
        --plain)    PLAIN=1 ;;
        --raw)      RAW=1 ;;
        --version)  printf 'synonyme.sh %s\n' "$VERSION"; exit 0 ;;
        -h|--help)  usage; exit 0 ;;
        --)         shift; WORDS+=("$@"); break ;;
        -*)         printf 'Unbekannte Option: %s\n\n' "$1" >&2; usage >&2; exit 2 ;;
        *)          WORDS+=("$1") ;;
    esac
    shift
done

[[ "${#WORDS[@]}" -gt 0 ]] || { usage >&2; exit 2; }
[[ "$LIMIT" =~ ^[0-9]+$ ]] || die "--limit braucht eine Zahl."
command -v curl >/dev/null || die "curl nicht gefunden (apt install curl)."

QUERY="${WORDS[*]}"

# ---- Abfrage -------------------------------------------------
# --data-urlencode übernimmt die Kodierung — wichtig für Umlaute und Leerzeichen.
RESPONSE=$(curl -fsSG --max-time "$TIMEOUT" \
    --data-urlencode "q=${QUERY}" \
    --data "format=application/json" \
    "$API" 2>/dev/null) || die "OpenThesaurus nicht erreichbar."

if [[ "$RAW" -eq 1 ]]; then
    printf '%s\n' "$RESPONSE"
    exit 0
fi

# ---- Auswerten -----------------------------------------------
# Absichtlich ohne jq: das Paket ist auf einem frischen Debian nicht
# installiert, und für "term"-Felder reicht grep vollkommen.
mapfile -t TERMS < <(
    printf '%s' "$RESPONSE" \
        | grep -o '"term":"[^"]*"' \
        | cut -d'"' -f4 \
        | grep -vixF "$QUERY" \
        | awk '!seen[$0]++'
)

if [[ "${#TERMS[@]}" -eq 0 ]]; then
    printf 'Keine Synonyme gefunden für: %s\n' "$QUERY" >&2
    exit 1
fi

if [[ "$LIMIT" -gt 0 && "$LIMIT" -lt "${#TERMS[@]}" ]]; then
    TERMS=("${TERMS[@]:0:$LIMIT}")
fi

if [[ "$PLAIN" -eq 1 ]]; then
    printf '%s\n' "${TERMS[@]}"
    exit 0
fi

# Kommagetrennt, an die Terminalbreite umgebrochen — aber nur, wenn die
# Ausgabe wirklich in einem Terminal landet. In einer Pipe wäre der
# Umbruch nur störend.
LINE=$(printf '%s, ' "${TERMS[@]}")
LINE="${LINE%, }"

if [[ -t 1 ]] && command -v tput >/dev/null && COLS=$(tput cols 2>/dev/null) && [[ "$COLS" -gt 20 ]]; then
    printf '%s\n' "$LINE" | fold -s -w "$COLS"
else
    printf '%s\n' "$LINE"
fi
Ad