Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove shortest leading whitespace from all lines

Tags:

shell

I have some text with some leading whitespace on all lines. I want to remove the whitespace from the shortest line (if it's simpler, this requirement could be changed to the first line) and then remove the same amount of whitespace from all other lines.

E.g. I have this text:

    var flatten = function(result, next_array) {
        console.log('current result', result);
        return result.concat(next_array);
    };

    [1, [2], [3, 4]]
        .reduce(flatten, []);

And I want to result in this text:

var flatten = function(result, next_array) {
    console.log('current result', result);
    return result.concat(next_array);
};

[1, [2], [3, 4]]
    .reduce(flatten, []);

Basically, I want to shift the text over until there's at least one line with no whitespace on the left and preserve all other leading whitespace on all other lines.

The use case for this is copying code from the middle of a section of code to paste as an example elsewhere. What I currently do is copy the code, paste into vim with paste insert mode, use << until I get the desired output, and copy the buffer. The same could be done in TextMate with Cmd-[.

What I want is to do this with a shell script so I could, for example, trigger it with a hotkey to take my clipboard contents, remove the desired whitespace, and paste the result.

like image 989
Thomas Upton Avatar asked Oct 11 '13 23:10

Thomas Upton


Video Answer


2 Answers

this awk one-liner could do it for you too. it assumes you want to remove at least 1 whitespace. (because I see in your example, there is an empty line, without any leading spaces, but all lines are shifted left anyway.)

test with your example:

kent$  cat f
    var flatten = function(result, next_array) {
        console.log('current result', result);
        return result.concat(next_array);
    };

    [1, [2], [3, 4]]
        .reduce(flatten, []);

kent$  awk  -F '\\S.*' '{l=length($1);if(l>0){if(NR==1)s=l; else s=s>l?l:s;}a[NR]=$0}END{for(i=1;i<=NR;i++){sub("^ {"s"}","",a[i]);print a[i]}}' f
var flatten = function(result, next_array) {
    console.log('current result', result);
    return result.concat(next_array);
};

[1, [2], [3, 4]]
    .reduce(flatten, []);

EDIT

I don't think awk scripts not readable. but you have to know the syntax of awk script. anyway, I am adding some explanation:

The awk script has two blocks, the first block was executed when each line of your file was read. The END block was executed after the last line of your file was read. See commpents below for explanation.

awk  -F '\\S.*'          #using a delimiter '\\S.*'(regex). the first non-empty char till the end of line
                         #so that each line was separated into two fields,
                         #the field1: leading spaces
                         #and the field2: the rest texts

'{                       #block 1
l=length($1);            #get the length of field1($1), which is the leading spaces, save to l
if(l>0){                 #if l >0
if(NR==1)s=l;            #NR is the line number, if it was the first line, s was not set yet, then let s=l
else s=s>l?l:s;}         #else if l<s, let s=l otherwise keep s value
a[NR]=$0                 #after reading each line, save the line in a array with lineNo. as index
}                        #this block is just for get the number of "shortest" leading spaces, in s

END{for(i=1;i<=NR;i++){  #loop from lineNo 1-last from the array a
sub("^ {"s"}","",a[i]);  #replace s number of leading spaces with empty
print a[i]}              #print the array element (after replacement)
}' file                  #file is the input file
like image 81
Kent Avatar answered Nov 04 '22 23:11

Kent


These functions can be defined in your .bash_profile to have access to them anywhere, rather than creating a script file. They don't require first line to match:

shiftleft(){
    len=$(grep -e "^[[:space:]]*$" -v $1 | sed -E 's/([^ ]).*/x/' | sort -r | head -1 | wc -c)
    cut -c $(($len-1))- $1
}

Usage: shiftleft myfile.txt

This works with a file, but would have to be modified to work with pbpaste piped to it...

NOTE: Definitely inspired by the answer of @JoSo but fixes the errors in there. (Uses sort -r and cut -c N- and missing $ on len and doesn't get hung up by blank lines w/o white space.)


EDIT: A version to work with the contents of the clipboard on OSX:

shiftclip(){
    len=$(pbpaste | grep -e "^[[:space:]]*$" -v | sed -E 's/([^ ]).*/x/' | sort -r | head -1 | wc -c)
    pbpaste | cut -c $(($len-1))-
}

Usage for this version: Copy the text of interest and then type shiftclip. To copy output directly back to the clipboard, do shiftclip | pbcopy

like image 20
beroe Avatar answered Nov 04 '22 23:11

beroe