Data rarely arrives in the shape the next tool wants. Contacts are buried in documents, domains sit in one column of a tab-separated export, a platform wants a comma-separated field, a script wants every value quoted, and an ad platform wants match-type symbols around every keyword.
This guide covers SeoWolf's tools for extracting, splitting, joining and formatting lists β with the edge cases that corrupt data and the real workflows they speed up.
The Tools at a Glance
| Tool | Use it to |
|---|---|
| Extract Email Addresses from Text | This scans text and returns every email address it finds, discarding the surrounding content. |
| Extract Unique Words | This reduces a body of text to the distinct words it contains β each one listed once, regardless of how many times it appeared. |
| Split a Delimited String into Lines | Data arrives glued together more often than it arrives clean. |
| Split by Delimiter / Extract Column | This splits each line on a delimiter and returns a chosen field β the third comma-separated value from every row, for instance. |
| Join Lines into a Delimited String | This collapses a multi-line list into a single line with a delimiter between values β the reverse of splitting. |
| Convert Tab-Separated Text to Lines | Copying a row out of a spreadsheet gives you tab-separated values on one line. |
| Add Prefix | Adding the same text to the front of every line sounds like the least interesting operation available. |
| Add Suffix | Appending identical text to the end of every line in a list is the mirror image of prefixing, and it comes up just as often β usually when a list of values needs a shared ending before another system will accept it. |
| Wrap Lines in Quotes | This surrounds each line with quote characters, turning a plain list into quoted values. |
| Alphabetize Words Within Each Line | This one sorts the words within each line while leaving the order of the lines themselves untouched. |
Extract Email Addresses from Text
What It Does
This scans text and returns every email address it finds, discarding the surrounding content.
Typical sources
Exported contact records, a document listing contributors, a page of published contacts, or a thread where addresses are scattered through the replies.
Clean up afterwards
Extracted lists almost always contain duplicates and often contain role addresses like info@ and support@ that may not be the ones you want. Deduplicate, then filter.
Use it lawfully
Extraction is a data-handling operation, and what you may do with the results is governed by privacy law and anti-spam regulation regardless of how the addresses were obtained.
No Regex Fully Validates an Email Address
The grammar for a valid address permits quoted strings, comments and nested structures. The RFC-complete regular expression is thousands of characters long and still does not tell you whether the address exists.
Extraction and validation are different jobs
For pulling addresses out of prose, a pragmatic pattern β a run of permitted characters, an @, a domain with at least one dot β is correct and sufficient. Trying to make the extraction pattern also enforce full validity makes it worse at both.
Common false positives
Twitter handles, version strings and file paths can all resemble addresses in loose patterns. Trailing punctuation attaches to the match, and a sentence ending immediately after an address leaves a period on the end of the domain.
The only real validation is delivery
Syntax checks confirm the address is well formed. They cannot confirm the mailbox exists. Where deliverability matters, use a verification service, and treat the syntax check purely as a cheap first filter.
In a Real Workflow: Building an Outreach List That Stays Lawful
Extracting addresses is trivial. Deciding which of them you may lawfully contact is the part that matters, and getting it wrong carries real penalties.
Scraping and sending are different questions
Finding a published address is not consent to market to it. Under GDPR and similar regimes, contacting an individual for marketing generally requires a lawful basis, and "it was on their website" is not automatically one.
Role addresses versus named individuals
A published press@ or editorial@ address exists to receive relevant approaches and sits on firmer ground than a scraped personal address. Prefer role addresses for cold outreach.
Relevance is the practical test
A genuinely relevant, individually written approach to a published business address is defensible and works. A bulk send to two thousand scraped addresses is neither, and it will damage the sending domain's reputation before it damages anything else.
Try it: Extract Email Addresses from Text on SeoWolf's Notepad.
Extract Unique Words
What It Does
This reduces a body of text to the distinct words it contains β each one listed once, regardless of how many times it appeared.
Vocabulary rather than volume
A two-thousand-word article might use only six hundred distinct words. That smaller set is the article's actual vocabulary, and it is a far more useful thing to inspect than the full text when the question is what a page is about.
Common uses
Building a term list from competitor content, checking whether a draft ever uses the terminology its audience uses, or finding the unexpected words that reveal what a page really covers.
Pair it with frequency
Unique extraction tells you which words are present. A frequency count tells you which ones dominate. The two together are a rough but genuinely useful topic profile.
Uniqueness Depends on How You Normalise Words
Extracting distinct words is a set operation over tokens, and every decision about what counts as the same token changes the result.
Case folding is the obvious one
Without it, SEO, Seo and seo are three entries. Lowercasing merges them β and also merges US the country with us the pronoun, which for a term-frequency profile is usually an acceptable loss.
Stemming versus lemmatisation
run, runs, running are three tokens and one concept. Stemming chops suffixes mechanically and is fast, but produces non-words: running becomes run, while business becomes busi. Lemmatisation maps to real dictionary forms and needs a language model behind it. For quick content profiling, stemming is usually enough; for anything user-facing, its output is too ugly to display.
Unicode normalisation before comparison
The same accented character has multiple valid byte representations. Two visually identical words can compare as different unless both are normalised to the same form first β normalise to NFC before building the set, or the count is quietly wrong on any non-English text.
In a Real Workflow: Checking Whether Your Draft Speaks the Customer's Language
A page can be well written, accurate, and still fail β because it describes the product in the company's vocabulary rather than the customer's. That gap is easy to feel and hard to prove.
Making it measurable
Extract the unique words from your draft. Extract them from the top three ranking pages for your target query. Compare the two sets. The terms present in theirs and absent from yours are the gap, stated concretely.
Reading the difference honestly
Not every missing word matters. What matters is when the missing terms are the ones customers actually use β the informal name for the product category, the problem phrased the way a person would phrase it, the comparison term they reach for.
What not to do with this
This is a prompt for judgement, not a checklist to satisfy. Stuffing in every absent term produces text that reads like it was assembled to pass an audit, because it was. Add the terms that belong, and only where a sentence is genuinely improved by them.
Try it: Extract Unique Words on SeoWolf's Notepad.
Split a Delimited String into Lines
What It Does
Data arrives glued together more often than it arrives clean. A comma-separated blob in a single cell, a pipe-delimited string from a log, a semicolon list pasted out of a form field β all of it needs breaking into one item per line before anything else can process it.
What the split does
You specify the delimiter and every occurrence becomes a line break. One long string becomes a proper list.
Why this is the first step so often
Almost every other list operation β sorting, deduplicating, filtering, counting β assumes one item per line. Splitting is the pass that makes the data eligible for all of them.
The reverse exists too
When the processed list needs to go back into a single field, joining it back into a delimited string is the same operation in reverse.
Splitting on a Delimiter Is Not the Same as Parsing CSV
tr ',' '\n' splits on commas. For a simple list that is correct and instant. For anything claiming to be CSV, it is a bug waiting to be discovered downstream.
Quoted fields contain delimiters
"Smith, John",42 is two fields. Split naively on the comma and it becomes three, with a stray quote on two of them. Every row containing a quoted comma shifts its columns by one, so the damage is silent and structural rather than obvious.
Escaped quotes inside quoted fields
CSV escapes a quote by doubling it, so "He said ""hello""" is one field. No delimiter-splitting approach handles that; it needs an actual parser tracking whether it is currently inside a quoted region.
The rule of thumb
If the data is a plain list of values that will never contain the delimiter β keyword lists, URL lists, ID lists β splitting is correct and a parser is overkill. The moment values might contain the delimiter, use a real CSV reader. The failure mode of getting this wrong is not an error message; it is quietly misaligned data that looks plausible.
In a Real Workflow: The Keyword List That Arrived as One Giant Cell
A client sends the keyword research. It is a spreadsheet with one populated cell containing eleven hundred comma-separated terms. Every tool you want to use needs one term per line.
The wrong instinct
The temptation is to start splitting it manually, or to hand it back and ask for a proper export. Both cost a day β the first in tedium, the second in waiting.
The thirty-second version
Paste the blob, split on the comma, and you have eleven hundred lines. Trim whitespace, since comma-separated data almost always carries a leading space on every item after the first, and the list is ready to work with.
What to do immediately after
Deduplicate. Lists assembled by hand into a single cell are almost always full of repeats, and the count you report back is more useful when it reflects distinct terms rather than distinct commas.
Try it: Split a Delimited String into Lines on SeoWolf's Notepad.
Split by Delimiter / Extract Column
What It Does
This splits each line on a delimiter and returns a chosen field β the third comma-separated value from every row, for instance.
Reducing a table to a column
Pasted tabular data contains several fields per line when the task needs only one. Extracting that field gives a clean single-value list.
Typical need
Pulling just the URLs out of a report that also carries positions, volumes and dates, so the URLs can be fed into something that expects a bare list.
Faster than a spreadsheet round trip
Copy the rows, extract the column, work with the result. No import dialog, no delimiter settings, no temporary file.
cut Is Not a CSV Parser
cut -d, -f3 extracts the third comma-separated field. It is fast, and it is wrong for any CSV where a field might contain the delimiter.
Quoted fields shift every column after them
"Smith, John",42,london has three fields. cut sees four, so field three is 42 rather than london. Only rows containing a quoted comma are affected, so the output is mostly correct β which makes the error far harder to notice than a total failure.
awk has the same limitation
awk -F, splits identically and shares the problem. Neither tool tracks quoting state, because neither is a CSV parser.
When each choice is right
For genuinely simple data β tab-separated, or comma-separated values that provably contain no commas β cut is correct and fastest. For anything untrusted, use a real parser: csvcut from csvkit, or a library in whichever language is to hand. The cost of the parser is a fraction of the cost of discovering misaligned columns three steps downstream.
In a Real Workflow: Getting the URLs Out of a Rank Tracking Export
A rank tracker exports keyword, position, volume, URL and date. You need the URLs, deduplicated, to see which pages are actually carrying the rankings.
Two steps
Extract the URL column, then deduplicate. A thousand-row keyword report frequently collapses to sixty or seventy distinct pages.
What the collapse tells you
If a thousand rankings come from sixty pages, a small number of pages are doing nearly all the work. Those pages deserve disproportionate protection and attention β and they are usually not the ones anyone is currently focused on.
Count rankings per page
Rather than deduplicating, count how many times each URL appears. That ranks your pages by how much of the site's search visibility each one carries, which is a more useful priority list than traffic alone.
Try it: Split by Delimiter / Extract Column on SeoWolf's Notepad.
Join Lines into a Delimited String
What It Does
This collapses a multi-line list into a single line with a delimiter between values β the reverse of splitting.
Why the reverse trip is needed
Lists are convenient for processing and inconvenient for input. Many fields, query parameters and configuration values want one string with separators, not many lines.
Choose the delimiter for the destination
Commas for a spreadsheet cell or an IN clause, pipes for systems where values might contain commas, spaces for a command line.
The round trip
Split, process, join. That sequence covers a large share of everyday list work: break the blob apart, clean it, put it back together.
Joining Is Safe Only If Values Lack the Delimiter
paste -sd, - joins lines with commas. The operation is trivial and, like quoting, is only correct under an assumption about the data.
The delimiter collision
A value containing a comma, joined with commas, is indistinguishable from two values. The output is well-formed and wrong, and splitting it again does not recover the original. Check whether the delimiter occurs in any value before choosing it β or pick one that cannot occur, which is why pipes and tabs are common choices for machine-readable output.
Trailing separators break parsers
Building the string by appending value + delimiter in a loop leaves a trailing delimiter, which many parsers read as a final empty value. Use the language's join on an array; it places separators between elements rather than after each one, which is the correct semantics and removes the whole class of bug.
Empty input is a real case
Joining an empty list gives an empty string, which as a query parameter or an IN clause is a syntax error rather than an empty set. Handle the zero-length case explicitly instead of letting it produce malformed output downstream.
In a Real Workflow: Getting a Cleaned List Back Into the Tool That Needs It
You split a blob into lines, deduplicated it, filtered it and sorted it. Now the platform you need to paste it into wants a single comma-separated string.
Closing the loop
Joining puts the cleaned list back into the shape the destination expects. Without it, the processing work sits in a format nothing downstream can consume.
Negative keyword lists are the common case
Ad platforms often accept a delimited string. Building the list properly β deduplicated, checked against your actual search terms report β and then joining it produces one paste instead of hundreds of individual entries.
Mind the field limits
Some fields cap the total character count rather than the number of values. If the joined string is rejected, split the list into batches and join each batch separately rather than trimming values arbitrarily.
Try it: Join Lines into a Delimited String on SeoWolf's Notepad.
Convert Tab-Separated Text to Lines
What It Does
Copying a row out of a spreadsheet gives you tab-separated values on one line. This converts each tab into a line break, turning that row into a vertical list.
Why tabs specifically
Tab is what spreadsheets use when data crosses the clipboard. Copy a range from Excel or Google Sheets, paste it anywhere that is not a spreadsheet, and tabs are what you get.
The usual need
A tool expects one value per line. The spreadsheet supplies one row of many columns. This converts between the two without a round trip through another application.
Rows versus columns
Copying a column already produces one value per line and needs no conversion. This is for the case where the values arrived side by side.
Tabs Survive the Clipboard Better Than Commas
TSV is the clipboard format for tabular data, and it is a genuinely better interchange format than CSV for one reason: the delimiter almost never appears in the values.
Why that matters
CSV needs quoting rules because commas appear in ordinary text, and quoting rules need escaping rules for the quotes themselves. Tabs rarely occur inside a cell, so TSV usually needs no quoting at all β which removes the entire class of parsing bugs that CSV invites.
Where it still breaks
A value containing a literal tab or newline breaks TSV completely, and there is no universally agreed escaping convention to fall back on. Multi-line cells pasted out of a spreadsheet are the common case, and they corrupt the row structure silently.
Empty fields are preserved, if you let them
tr '\t' '\n' converts tabs to newlines but tr -s squeezes repeats, collapsing consecutive tabs into one β which silently drops empty columns and shifts everything after them. Use tr without -s when column positions matter.
In a Real Workflow: Moving Data Between a Spreadsheet and Everything Else
Half of marketing work is getting data out of a spreadsheet and into a tool that wants a plain list, then back again. The tab is what makes that transfer awkward.
The forward direction
Copy a row of forty campaign names, convert tabs to lines, and you have a list ready for whatever needs one per row. No intermediate file, no export dialog, no CSV settings to negotiate.
The return trip
Once the list has been sorted, filtered or deduplicated, joining it back into a tab-separated string pastes cleanly back across a spreadsheet row.
The habit worth forming
Treat the spreadsheet as storage and the list tools as the working surface. Trying to do list manipulation inside a spreadsheet means writing formulas for operations that are a single click elsewhere, and formulas are where subtle errors live.
Try it: Convert Tab-Separated Text to Lines on SeoWolf's Notepad.
Add Prefix
What It Does
Adding the same text to the front of every line sounds like the least interesting operation available. It is also one of the most frequently needed, because so much marketing data arrives as bare fragments that need context attached before anything else can use it.
The shape of the problem
An export gives you /blog/post-one, /blog/post-two, and so on. What you need is a full URL. The missing piece is identical on every line, which is exactly the case a prefix tool exists for.
Applied to the whole list at once
The rule is applied uniformly in a single pass. There is no scrolling, no click-and-type per row, and no risk that line 340 quietly received a slightly different prefix than line 12.
Where it usually sits
Prefixing is rarely the whole job. It is normally one step before a deduplication pass, a sort, or an import into another platform that insists on absolute values.
Line Prefixing and the Trailing Newline Problem
sed 's|^|PREFIX|' prefixes every line, and for a well-formed file that is the end of it. Real exports are rarely well formed.
Blank lines are still lines
^ matches at the start of an empty line too, so a file with trailing blank lines comes back with prefixes attached to nothing. If that output feeds a URL list, you have just manufactured a request to your own domain root, repeated however many empty lines the export happened to end with. Filter blanks first, not after.
Carriage returns from Windows exports
A file saved on Windows ends its lines with \r\n. Process it on Linux and the \r\n stays put, sitting invisibly at the end of every value. The prefix looks correct in the terminal and breaks the moment the data is compared against anything. sed 's/\r$//' before the prefix pass, or the whole batch silently mismatches.
The encoding trap
If the prefix itself contains & in a sed replacement, it expands to the entire matched text rather than a literal ampersand. Escape it, or use a tool where the input is treated as a literal string rather than a pattern.
In a Real Workflow: Turning a Crawl Export Into Something You Can Actually Send
A crawl finishes and hands you eight hundred relative paths. The developer who has to action them needs full URLs, and pasting a spreadsheet of /category/thing into a ticket is a good way to have it bounced back.
The five-second version of a ten-minute job
Paste the paths, set the prefix to https://yoursite.com, and every line becomes clickable. The person receiving the ticket can now open any of them without reconstructing the address in their head.
Why this matters more than it sounds
Friction in a handoff is where fixes go to die. A list of relative paths asks the recipient to do work before they can start the actual work, and that reliably converts an urgent ticket into a stale one. A list of live links does not.
The same move in other places
Ad platforms wanting fully-qualified destination URLs, redirect maps needing absolute targets, outreach lists where every row needs the same UTM tail β all the same operation, applied at the other end.
Try it: Add Prefix on SeoWolf's Notepad.
Add Suffix
What It Does
Appending identical text to the end of every line in a list is the mirror image of prefixing, and it comes up just as often β usually when a list of values needs a shared ending before another system will accept it.
Typical shapes
A list of page paths that all need /index.html appended. A set of keywords that all need the same qualifier attached. A column of identifiers that need a file extension before a bulk upload will recognise them.
Consistency is the point
Doing this by hand across a few hundred lines will produce at least one line with a typo or a missing character, and that line will be the one that breaks the import. A single-pass rule cannot make that mistake.
Order matters when combining
If a list needs both a prefix and a suffix, apply them as two clean passes rather than trying to reconstruct each line manually. Each pass is verifiable on its own.
Appending to Line Ends Without Breaking Existing Queries
sed 's|$|SUFFIX|' appends to every line. The operation is trivial; deciding what to append is where it gets interesting, particularly with URLs.
The query-string branch
Appending ?utm_source=x is correct only if the URL has no query string already. If it does, the correct separator is &, and a blind append produces a URL with two ? characters. Most servers parse that as a single parameter with a literal ? inside its value, which means the tracking silently does not work rather than visibly failing.
Anything appending tracking parameters at scale needs to branch on whether ? is already present. A flat append does not.
Fragments come last
#section must remain the final component of a URL. Append a query string to a URL that already ends in a fragment and the parameters end up inside the fragment, where the server never sees them at all.
The safe sequencing
Strip query strings, append the known-good parameter set, then reattach fragments if you need them. Three deterministic passes beat one clever regex that handles the common case and mangles the rest.
In a Real Workflow: Tagging a Launch List With One Consistent UTM String
Campaign links are only as good as the tracking on them, and tracking is only useful if it is identical across every link in the campaign. One mistyped parameter and that click lands in a bucket nobody looks at.
The realistic failure
Someone builds forty campaign URLs by hand over two days. Thirty-eight carry utm_campaign=spring-launch. Two carry spring_launch and springlaunch. In the report, the campaign appears to have three separate identities, none of which show the real number.
Doing it in one pass instead
Paste the forty destination URLs, append the full UTM string once, and every link in the campaign is now provably identical from the ? onward. There is no version of that pass that produces two spellings.
Check before you ship
Run the finished list past a duplicate check. If any two lines match exactly, you have pasted the same destination twice β which is worth catching before the links go into an email that cannot be recalled.
Try it: Add Suffix on SeoWolf's Notepad.
Wrap Lines in Quotes
What It Does
This surrounds each line with quote characters, turning a plain list into quoted values.
Where quoted values are required
SQL IN clauses, JSON and JavaScript arrays, CSV fields containing commas, and several ad platform bulk formats all expect quoted strings.
The reason quoting exists
Quotes mark where a value begins and ends, so a value containing the delimiter does not split into two. Without them, Smith, John becomes two fields.
Combine with joining
Quoting each line and then joining with commas produces a ready-made list for a query or an array literal in a single pair of operations.
Quoting Without Escaping Is an Injection Waiting to Happen
Wrapping values in quotes is only safe if the values cannot contain the quote character. When they can, quoting alone produces broken syntax at best and an injection vector at worst.
The escape rules differ per format
SQL escapes a single quote by doubling it. JSON escapes with a backslash, and also requires escaping backslashes, control characters and certain Unicode ranges. CSV doubles the quote. Applying one format's rule to another produces output that parses incorrectly or not at all.
Never build queries by string concatenation
A quoted list pasted into a query is fine for a one-off exploration by a human who can see the data. In application code, use parameterised queries β the parameter binding handles escaping correctly for every value, which no amount of manual quoting reliably does.
Serialise rather than assemble
For JSON, use the language's serialiser on an array rather than constructing the literal by wrapping and joining. It handles every escape case, including the ones you have not thought of, and costs one line.
In a Real Workflow: Handing a Filter List to an Analyst Without a Follow-Up
You need traffic data for three hundred specific URLs. The analyst needs those URLs in a form they can paste into a query, and a plain list is not it.
What they actually need
A quoted, comma-separated list that drops straight into a WHERE ... IN (...) clause. Supplying that instead of a bare list removes a step from their work and a round trip from yours.
Why this is worth caring about
Requests that arrive ready to run get answered the same day. Requests that need reformatting first join a queue. The difference is thirty seconds of your time against days of waiting.
Watch for apostrophes
Values containing an apostrophe β anything with a possessive or a French place name β break single-quoted SQL unless escaped. Flag those to the analyst rather than letting a query fail in a way that looks like their problem.
Try it: Wrap Lines in Quotes on SeoWolf's Notepad.
Alphabetize Words Within Each Line
What It Does
This one sorts the words within each line while leaving the order of the lines themselves untouched. It is a narrow operation with one very specific use: making phrases that contain the same words look identical.
What it produces
red running shoes and running shoes red both become red running shoes. The lines are now byte-identical, which means any tool that compares strings will finally agree they are the same thing.
The term for this
Two phrases built from the same words in a different order are permutations of one another. Sorting the words inside each line is how you collapse every permutation onto a single canonical form.
What it is not for
This scrambles readability. It is a comparison aid, not an output format β you sort words to find the duplicates, then work with the original phrasing.
Canonicalising Phrases by Sorting Their Tokens
Sorting tokens within a record to produce a comparable key is a standard normalisation move β the same idea behind sorting a set before hashing it. The implementation details are where results diverge.
Your sort is locale-dependent
Plain byte sorting puts every uppercase letter before every lowercase one, so Paris flights and flights Paris do not converge. Case-fold before sorting, or the whole exercise produces false negatives on any list with inconsistent capitalisation.
Beyond ASCII it gets worse: Γ© sorts after z bytewise but next to e under a locale-aware collation. For keyword data in any European language, that distinction decides whether the canonical forms match.
Whitespace defines your tokens
Splitting on a single space breaks on double spaces and tabs, producing empty tokens that sort to the front and leave a leading space in the output. Split on runs of whitespace, and normalise the separator on the way out.
Punctuation attaches to words
shoes, and shoes are different tokens. Strip punctuation before sorting if the goal is comparison, or accept that a stray comma will keep two otherwise-identical phrases apart.
In a Real Workflow: Finding the Keyword Duplicates a Dedupe Pass Misses
A keyword list of four thousand terms comes back from deduplication at 3,900. It still contains hundreds of duplicates, because exact-match deduplication only catches identical strings and search intent does not care about word order.
The duplicates hiding in plain sight
cheap flights to paris, flights to paris cheap and paris flights cheap are three rows in the export and one search intent. Build content plans from the raw list and you will commission three briefs for the same page.
Collapsing them
Sort the words inside each line, then run an exact deduplication over the result. Every permutation of the same word set collapses to one row, and the true size of the list appears β often noticeably smaller than the tool that generated it claimed.
Use the count, keep the originals
Work from the collapsed list to decide how many pages you need. Go back to the original phrasing when writing them β real people search in natural word order, and the alphabetised version is a counting device, not copy.
Try it: Alphabetize Words Within Each Line on SeoWolf's Notepad.
The Bottom Line
Formatting tools change structure, so check the edges: delimiters inside values, empty fields, quotes within text and trailing separators. A quick look at the first and last few lines catches most problems before they reach an import.
Next step: All of these tools are free in SeoWolf's Notepad β paste a list and transform it in your browser.