banner

For a full list of BASHing data blog posts see the index page.  RSS


Yet another gremlin: the zero-width space

The gremlin detector script in A Data Cleaner's Cookbook now looks for zero width spaces (U+200B, hex e2 80 8b, ​).

Like a soft hyphen (SHY), a zero-width space (ZWSP) is usually non-printing and invisible, and indicates for a Web browser or word-processing program where a string of visible characters can be broken when wrapping a line. With SHY, the program will add a hyphen at the end of the wrapped line, while a ZWSP just means "You can break the string here when wrapping".

Also like a SHY (and a no-break space (NBSP)), a ZWSP can cause problems where it isn't needed. A quick googling for "problem with zero width space" turned up ZWSP-caused issues with

A ZWSP is a nuisance in data auditing because it's a formatting character that can hide duplicates in data content. In the second case below, there's a ZWSP in one of the "aaa" strings. The two "aaa" lines are no longer recognised as duplicates by uniq:

zwsp1

For locating scattered, invisible characters like ZWSPs in the data files I audit (they're all TSVs, UTF-8 encoded), I've started using the "heremark" function below, which accepts a Unicode character as "uxxxx" in its second argument. "heremark" is similar to the gremfinder script but replaces the character with the marker "{HERE}".

heremark() { char=$(printf "\\$2"); awk -F"\t" -v find="$char" '$0 ~ find {for (i=1;i<=NF;i++) if ($i ~ find) {gsub(find,"\33[31;1m{HERE}\33[0m",$i); print "line "NR", field "i":\n"$i}}' "$1"; }

The second argument is stored in the variable "char" together with a backslash as prefix (char=$(printf "\\$2")).
 
The shell variable "char" is assigned the AWK variable "find" (-v find="$char"), and note that in this function, AWK is told that the field separator is a tab (-F"\t").
 
AWK looks for and selects in the first argument ("$1") just those lines containing the looked-for character ($0 ~ find), and loops through the fields in that line one by one (for (i=1;i<=NF;i++))
 
If any of the fields in that line contains the character (if ($i ~ find) ), AWK replaces all instances of the character in that field with "{HERE}" in bold and red (gsub(find,"\33[31;1m{HERE}\33[0m",$i)). AWK then prints the line number and the field number, followed (next line) by the field with its replaced characters (print "line "NR", field "i":\n"$i).

The file "demo" has a ZWSP buried in the "eee" and "ooo" strings:

zwsp2

An easy way to delete all ZWSPs in a file is with sed:

sed 's/\xe2\x80\x8b//g' file > file_without_ZWSPs
sed $'s/\u200b//' file > file_without_ZWSPs

The second command is explained in this BASHing data post.

There's also a webpage devoted specifically to removing ZWSPs from pasted-in strings! It's based on a (java)script with the instruction input.replace(/\u200B/g, "").


No-break spaceNBSPu00a0
Soft hyphenSHYu00ad
Zero-width spaceZWSPu200b

Last update: 2021-09-01
The blog posts on this website are licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License