13

Is there a command or set of commands that I can use to horizontally align lines of text to an arbitrary character? For example, with a list of email addresses the output would produce a text file with all the '@' characters lined up vertically.

To be successful I believe that a variable number of empty spaces must be added to the beginning of most lines. I do not want separate columns as they take more effort to read (for example, column -t -s "@" < file.txt).

Before:

123@example.com
456789@example.net
01234@something-else.com

After:

   123@example.com
456789@example.net
 01234@something-else.com

Put differently: can I specify a character to be an anchor point, around which the surrounding text is horizontally centered? My use-case for this is email addresses, to make them easier to scan visually.

  • 1
    What should happen if there are multiple @ symbols? – Zeta Dec 10 '17 at 14:15
  • Good question, multiple @ symbols shouldn't be an issue with email addresses but a user should be able to select which instance of a character per line to be the 'anchor' around which the other text is centered. – Tom Brossman Dec 10 '17 at 19:20
  • 1
    Multiple @ symbols are allowed in email addresses, e.g. tom"@brossmann"@example.com. That's why I asked what should happen if there are multiple @ symbols :). – Zeta Dec 10 '17 at 19:25
  • @Zeta Multiple @ symbols are not allowed in a variety of email services. It's entirely reasonable to expect "normal" emails that fit a tighter standard than the "real" one, unless you're dealing with raw, unfiltered user input, in which case you're more likely to deal with lines with no @. – Nic Hartley Dec 11 '17 at 7:22
3

NO Awk. Only sed and column:

column -ts@ file.txt | sed -E 's/([^ ]+)([ ]+) (.+)/\2\1@\3/'

Output:

   123@example.com
456789@example.net
 01234@something-else.com

Now, that I think about, this is almost the same as Sundeep' solution, it just looks shorter / has less calls to sed, and it also assumes that @ happens only once in each line.

  • 1
    It can be even more short: column -ts@ input.txt | sed -r 's/([^ ]+)( *)\s\s/\2\1@/' – MiniMax Dec 10 '17 at 18:21
11

At its simplest, you could just print the first field in a suitably large fieldwidth e.g.

awk -F@ 'BEGIN{OFS=FS} {$1 = sprintf("%12s", $1)} 1' file
         123@example.com
      456789@example.net
       01234@something-else.com

AFAIK any method that does not assume a specific maximum fieldwidth will require either holding the file in memory or making two passes.

  • good one, to get length one can also use cw=$(cut -d@ -f1 file | wc -L) and then awk -v w="$cw" 'BEGIN{OFS=FS="@"} {$1 = sprintf("%*s", w, $1)} 1' – Sundeep Dec 10 '17 at 15:13
  • Testing this against a list of 328 addresses, ten are somehow missing from the output (now 318 lines). For clarity, I ran awk -F@ '{a[$1] = $2; w = length($1) > w? length($1) : w; next} END {for (i in a) printf("%*s%c%s\n", w, i, FS, a[i])}' INPUT-FILE.txt > OUT.txt. It did format the remainder nicely, but some data is missing. – Tom Brossman Dec 10 '17 at 18:24
  • 1
    @TomBrossman thanks I just realized it has quite a serious flaw - it will not handle identical name fields - I'm going to delete that one – steeldriver Dec 10 '17 at 18:30
  • The same result, but more concisely awk -F@ '{printf "%12s@%s\n", $1, $2}' input.txt – MiniMax Dec 10 '17 at 18:48
6

hacky solution, assumes a lot about input text

$ # four commas to reduce chance of it affecting actual email address
$ sed 's/@/,,,,@/' ip.txt | column -t -s,,,,
123     @example.com
456789  @example.net
01234   @something-else.com

$ sed 's/@/,,,,@/' ip.txt | column -t -s,,,, | sed -E 's/^([^ ]+)( +)/\2\1/'
     123@example.com
  456789@example.net
   01234@something-else.com
4

A quick Python solution that uses the shortest possible padding length that right-aligns all strings left of the separator:

#!/usr/bin/env python3
import sys
fieldsep = '@'
records = [line.rstrip('\n').split(fieldsep, 1) for line in sys.stdin]
col1_len = max((len(r[0]) for r in records), default=0)
for r in records:
    print(r[0].rjust(col1_len), r[1], sep=fieldsep)

Usage:

python3 align-field.py < data.txt
2

Another GNU awk + column solution:

awk '{ split($0,a,/ +/,sep); printf "%*s@%s\n",length($1 sep[1])-2,$1,$2 }' <(column -ts'@' file)

The output:

   123@example.com
456789@example.net
 01234@something-else.com
  • Could you add a bit about how this one works? – Joe Dec 16 '17 at 5:51
2

This can work with Bash string manipulation, too.

Bash script (4.x):

#!/bin/bash

read -d '' -r -a data <"data.txt"

for ((pos=0, i=0; i<${#data[@]}; i++)); do
    locl=${data[$i]%@*}                         # The local-part.
    [[ ${#locl} -gt $pos ]] && pos=${#locl}     # Determine the lengthiest $locl.
done

for ((i=0; i<${#data[@]}; i++)); do
    email=${data[$i]}
    locl=${email%@*}                            # The local-part.
    domain=${email#*@}                          # The email domain.
    printf '%*s@%s\n' $pos $locl $domain        # Align $locl to the right, at $pos.
done

The result:

   123@example.com
456789@example.net
 01234@something-else.com

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.