6

Given this input:

# Lines starting with # stay the same
# Empty lines stay the same
# only lines with comments should change

ls  # show all major directories
              # and other things

cd      # The cd command - change directory  
            # will allow the user to change between file directories

touch             # The touch command, the make file command 
                # allows users to make files using the Linux CLI #  example, cd ~

bar foo baz # foo foo foo

I need to keep lines beginning with # and lines not containing any comments as is, but align all other comments on the same column.

Desired Output:

# Lines starting with # stay the same
# Empty lines stay the same
# Only lines with # in middle should change and be aligned

ls              # show all major directories
                # and other things

cd              # The cd command - change directory  
                # will allow the user to change between file directories

touch           # The touch command, the make file command 
                # allows users to make files using the Linux CLI #  exmaple, cd ~

bar foo baz     # foo foo foo

Here what I have so far:

# Building an array out of input
 while IFS=$'\n' read -r; do 
    lines+=("$REPLY") 
 done 

# Looping through array and selecting elemnts that need change 
for i in "${lines[@]}"
  do
    if  [[ ${i:0:1} == ';' || $i != *";"* ]];
      then
        echo "DOESNT CHANGE: #### $i"
    else 
        echo "HAS TO CHANGE: #### $i"
        array+=( "${i%%";"*}" );
        array2+=("${i##";"}")
    fi
done

# Trying to find the longest line to decide how much space I need to add for each element
max = ${array[0]}

for n in "${array[@]}" ; do
    ((${#n} > max)) && max=${#n}
    echo  "Length:" ${#n} ${n}
done

#Longest line
echo $max

# Loop for populating array 
for j in "${!array2[@]}" ; do
    echo "${array2[j]} " | sed -e "s/;/$(echo "-%20s ;") /g" 
done

I feel like I'm doing too much. I think there should be an easier way to tackle this problem.

14

If all your commands and arguments do not contain #, and one other character (say the ASCII character given by byte 1), you can insert that other character as an extra separator and use column to align the comments (see this answer). So, something like:

$ sed $'s/#/\001#/' input-file | column -ets $'\001'
# Lines starting with # stay the same
# Empty lines stay the same
# only lines with comments should change

ls                                        # show all major directories
                                          # and other things

cd                                        # The cd command - change directory
                                          # will allow the user to change between file directories

touch                                     # The touch command, the make file command
                                          # allows users to make files using the Linux CLI #  example, cd ~

bar foo baz                               # foo foo foo

If your column doesn't support -e to avoid eliminating empty lines, you could add something to empty lines (for example, a space, or the separator character used above):

$ sed $'s/#/\001#/;s/^$/\001/' input-file | column -ts $'\001'
# Lines starting with # stay the same
# Empty lines stay the same
# only lines with comments should change

ls                                        # show all major directories
                                          # and other things

cd                                        # The cd command - change directory
                                          # will allow the user to change between file directories

touch                                     # The touch command, the make file command
                                          # allows users to make files using the Linux CLI #  example, cd ~

bar foo baz                               # foo foo foo
  • Thanks for replying but it keeps giving me "column: illegal option -- e " and when removed doesn't format the text as desired. – Caleb May 14 '18 at 5:35
  • @Caleb column eliminates empty lines by default, and the -e option is to prevent that. You could work around it by inserting the separator character in empty lines as well: sed $'s/#/\001#/;s/^\$/\001/' input-file | column -ts $'\001' – muru May 14 '18 at 5:37
5

Text processing with the shell alone is a bit awkward and may be error prone (see "Why is using a shell loop to process text considered bad practice?"). It's generally better to use and other programming language for tasks such as these.


perl -ne 'if (/^([^#]+?)\s*#(.*)$/) { printf("%-16s#%s\n", $1, $2) } else { print }' file

This uses Perl to capture the bit in front of the # (discarding spaces between the last word and the #) and the bit after. If the match was successful, it allocates 16 character locations for the text and prints the formatted text and comment. If the match was not successful (because the line was blank or started with an #), the line is printed without modification.

# Lines starting with # stay the same
# Empty lines stay the same
# only lines with comments should change

ls              # show all major directories
                # and other things

cd              # The cd command - change directory
                # will allow the user to change between file directories

touch           # The touch command, the make file command
                # allows users to make files using the Linux CLI #  example, cd ~

bar foo baz     # foo foo foo
3

Here's a Python script that should do what you want:

#!/usr/bin/env python
# -*- encoding: ascii -*-
"""align.py"""

import re
import sys

# Read the data from the file into a list
lines = []
with open(sys.argv[1], 'r') as textfile:
    lines = textfile.readlines()

# Iterate through the data once to get the maximum indentation
max_indentation = 0
comment_block = False
for line in lines:

    # Check for the end of a comment block
    if comment_block:
        if not re.match(r'^\s*#.*$', line):
            comment_block = False

    # Check for the beginning of a comment block
    else:
        if re.match(r'^[^#]*[^ #].*#.*$', line):
            comment_block = True
            indentation = line.index('#')
            max_indentation = max(max_indentation, indentation)

# Iterate through the data a second time and output the reformatted text
comment_block = False
for line in lines:
    if comment_block:
        if re.match(r'^\s*#.*$', line):
            line = ' ' * max_indentation + line.lstrip()
        else:
            comment_block = False
    else:
        if re.match(r'^[^#]*[^ #].*#.*$', line):
            pre, sep, suf = line.partition('#')
            line = pre.ljust(max_indentation) + sep + suf
            comment_block = True

    sys.stdout.write(line)

Run it like so:

python align.py input.txt

It produces the following output:

# Lines starting with # stay the same
# Empty lines stay the same
# only lines with comments should change

ls                # show all major directories
                  # and other things

cd                # The cd command - change directory  
                  # will allow the user to change between file directories

touch             # The touch command, the make file command 
                  # allows users to make files using the Linux CLI #  example, cd ~

bar foo baz       # foo foo foo

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.