0

I am trying to replace every instance of a block of six rows with another block of six rows, just reducing the six rows block of two columns to a six rows block of only one column.

Thank you to your comments I'll write this question more clearly. I have several guitar tablature text files each with many "lines" of music. Each "line" of music has 6 (as the guitar number of strings) consecutive rows of text. Between the "lines" of music there are empty (or with some text) lines. Normally these six row "lines" are about two hundred columns length, and I want to reduce the length of the lines by replacing each six vertical row of -- by a six vertical row of -.

As an example. I would like to reduce the first chunk to the second.

|------------------|    |-------|            
|------------------|    |-------|    
|-0----------------|    |-0-----|   
|-2-----2-----2----|    |-2-2-2-|     
|-------2----------|    |---2---|       
|-------------0----|    |-----0-| 

I have tried the following code (seen on unix.stackexchange 1) without success:

sed -i '/--/,/--/,/--/,/--/,/--/,/--/c\
-\
-\
-\
-\
-\
-' a.txt

I have had no better results with awk or vim or perl.

A partial solution, for example using awk with Field Separator empy, would be: find two consecutive columns all whose characters are either empty or "-". Then delete one of the two columns. But I do not know how to do it.

5
  • 2
    You mention in your first sentence that the file may have several columns, and that what you show is just one column out of many. Is that so? Commented Mar 10, 2019 at 19:29
  • 2
    Is that lines consisting literally of the two letters x and two backslashes? I'm not sure how that is two columns, can you edit the question to clarify? And if it's not literally xx<backslash><backslash>, can you add an example of what the data actually would be? Commented Mar 10, 2019 at 20:08
  • KIndly post expected ouput Commented Mar 11, 2019 at 20:48
  • Thank you for your comments. @Kusalananda: Yes, it is so.@ilkkachu: I have edited the question: it wasn't clear. Copying the chunk of "-" and "0" and "2" to a text editor with fixed length font, I think it is more clear what I would like to achieve. Commented Mar 12, 2019 at 16:49
  • @ocap, hmm, that particular tablature seems to be based on parts like -x----, where x is either the number of the fret or another -. Removing some of the dashes from that would be simple, one could do that by just looking at one line at a time. But the problems come if the same file contains tablatures with different spacings (different numbers of dashes between the notes). Especially since we can't tell from the first line how many time units there are... Commented Mar 12, 2019 at 19:16

5 Answers 5

2

The tablature you posted seems to be built on six-character long pieces, -x----, where x is either a fret number or a dash. Like so:

 123456123456123456
 -x-----x-----x----
|------------------|
|------------------|
|-0----------------|
|-2-----2-----2----|
|-------2----------|
|-------------0----|

If they're all with that spacing, you can just simply change the pieces to remove some dashes, without regard for other lines.

$ sed -Ee 's/-(.)----/-\1/g; s/\|/-|/2' < tab
|-------| 
|-------|
|-0-----|
|-2-2-2-|
|---2---|
|-----0-|

But if you have tabs written with different spacings in the same file, then it gets much harder, as we'd need to recognize the spacing for each tab, and in general, that can't even be done from the first line. (since it can be empty as in your example.)

The solutions I can come up with for the more general case involve decoding the whole tablature and then reprinting it and that won't fit in this answer.


Note that sed's /this/,/that/ syntax processes blocks of lines where the first line matches /this/, the last line matches /that/, and the block contains all lines in between. /--/,/--/,/--/,... is therefore nonsense, since there are more patterns than just the start and end of a block.

To match a block that looks like a tablature, you'd need to match the |- and then have the block extend for six lines. In GNU sed, you could use /^|-/,+6 for that.

2
  • Thank you ilkkachu. It does not work because my question was poorly written. I have rewritten again. Commented Mar 12, 2019 at 19:04
  • Thank you ilkkachu. I'll work all your advise to see if I find a solution. I have edited the question with a partial solution, that maybe could be implemented with awk. Commented Mar 13, 2019 at 8:09
1

In vim, use visual-block mode to highlight the columns to delete via

<C-v>

and your favorite movement commands (o and O jump to the corners of the block so you can adjust if you make a mistake).

Then type d.

0

I have done by below method

command

i=`awk '{print NR}' l.txt | sort -nr | sed -n '1p'`

 for ((j=1;j<=$i;j++)); do sed -n ''$j'p' filename|awk -F "" '{gsub("","\n",$0);print $0}'|sort | uniq;done| sed '/^$/d'| sed "N;s/\n/ /g"| awk '{print $NF,$1}'| sed -r "s/ //g"

output

x\
x\
x\
x\
x\
x\
1
  • Thank you Praveen Kumar BS. I do not understand all this code; I am a novice, but should this code be inside a bash script? Because I get an error in the "i=..." part when I run it from the command line. Commented Mar 13, 2019 at 8:13
0

Ahoy!

This is an interesting problem! I dont think you can solve this problem with awk or sed because you have to read the file into memory and manually iterate through each row and column of the data. The awk and sed solutions above are hardcoded for only the example input and will not work if the input file changes.

I was able to solve in Perl using the following steps:

  1. Put the data in two dimensional array @rows also known as a 2D matrix
  2. Starting with the first column $c, check column $c and $c+1
  3. Find any columns where $c and $c+1 have a dash - value for all rows
  4. Set these columns to 1 or TRUE in @columnToRemove
  5. Print all columns except those with a 1 or TRUE value in @columnToRemove

Here is the code...

#!/usr/bin/perl

my (@rows, @columnToRemove);

while(<>){
  next if(/^$/);       #skip blank lines
  chomp;                #remove newline
  s/\|//g;               #remove separator
  push(@rows,[split(//)]);#put data in two dimensional array
}

my ($rowLength, $columnLength) = ($#rows, $#{$rows[0]});

#find columns to delete, two consecutive columns with "-"
COLUMN: for my $c (0..$columnLength){
          #assume column can be deleted until non dash "-" is found in $c and $c+1
          $columnToRemove[$c] = 1;
          for my $r (0 .. $rowLength){
            if( !($rows[$r][$c] eq "-" && $rows[$r][$c+1] eq "-") ){ #removed warnings because $c+1 value can be undef
              #this column has data and can not be deleted
              #mark false and move to next column
              $columnToRemove[$c] = 0;
              next COLUMN;
            }
          }
        }

#put "|" seperator back around data
#do not print columns marked to remove in @columnToRemove
for my $r (0 .. $rowLength){
  print "|";
  for my $c (0 .. $columnLength){
    print $rows[$r][$c] if(!$columnToRemove[$c]);
  }
  print "|\n";
}

Output looks like this...

$ cat condense.txt
|------------------|
|------------------|
|-0----------------|
|-2-----2-----2----|
|-------2----------|
|-------------0----|

$ perl condense.pl condense.txt
|-------|
|-------|
|-0-----|
|-2-2-2-|
|---2---|
|-----0-|

I wanted to check to make sure it worked, so I created some test files in the same format using the following script.

#!/usr/bin/perl -w

my ($rowLength, $columnLength) = (10,17);

for(0..$rowLength){
  print "|";
  for(0..$columnLength){
    my $n = int(rand 200); #high enough value for blank rows to delete
    if($n < 10){
      print "$n";
    }else{
      print "-";
    }
  }
  print "|\n";
}

Here is the command to generate a file to condense, print the generated file, then condense the file using the above scripts.

$ perl create.condense.file.pl | tee /dev/tty | perl condense.pl
|-----3-----------0|
|------------------|
|------------------|
|--1----4----------|
|----------------8-|
|---3--------------|
|------------------|
|------------------|
|------------------|
|------------------|
|----------8-------|
|----3------0|
|------------|
|------------|
|-1----4-----|
|----------8-|
|--3---------|
|------------|
|------------|
|------------|
|------------|
|--------8---|

To create a new file with the output, try the following command...

$ perl condense.pl condense.txt > updated.condense.txt

I think that is what you were looking for! Give it a try and let me know how it works or if something needs to be changed.

Good Luck!

1
  • Thank you very much. It works partially, because the file contains several lines of 6 strings all different. I had many guitar MIDI files that I converted to tab using a program which separates the notes, with minimum two hyphens (--), according with the rhythm, which made the measures all different sizes and very long, making them difficult to read. They could be converted manually. Knowing the melody and rhythm, it's easy to read even if the spacing between notes doesn't reflect the actual time. Now I have too many tabs. I really admire those who know how to use Perl. Commented Nov 26 at 10:55
0

With some advanced features of perl's regexps, you could do:

perl -0777 -pe '
  while (
    s{ ^ .*? (--+) .*+ \n
       (?:(??{ ".{$-[1]}" }) \1 .*+ \n){5}
    }{
      $& =~ s/^.{$-[1]}\K$1/-/mgr;
    }xe
  ) {}' your-file

Which looks for a sequence of two or more -s and the same (\1) sequence at the same position ($-[1]) in the 5 following lines, and for that matching set of 6 lines, removes those at that position. That's repeated until there's no more match.

Golfed:

perl -0777pe'1while s@^.*?(--+)(?:.*+\n(?:(??{".{$-[1]}"})\1)){5}@$&=~s/^.{$-[1]}\K$1/-/mgr@e' your-file

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.