I have a large piece of code consisting of thousands of equations. Example of two connective lines, corresponding to lines 998 and 999 is
sum(x[i]*y[i]for i in 1:n) == 15;
sum(z[i]*x[i]for i in 1:n) == 30;
I would like to replace such lines with the following
sum(x[i]*y[i]for i in 1:n) - 15 >= -s[998];
sum(x[i]*y[i]for i in 1:n) - 15 <= s[998];
sum(z[i]*x[i]for i in 1:n) - 30 >= -s[999];
sum(z[i]*x[i]for i in 1:n) - 30 <= s[999];
How to automate this process?
Substitutions
Our first step is to transform each individual line:
sum(x[i]*y[i]for i in 1:n) == 15;
into this:
sum(x[i]*y[i]for i in 1:n) - 15 >= -s[998];
While doing it in one command would be a nice parlor trick, we will do it in several easier to follow steps.
== to -
Here is our first command, it is very simple:
:%s/==/-<CR>
: enters command-line mode.
% is the range of lines on which to execute the following command. Here, % is a shortcut for 1,$ (from line 1 to last line), thus "every line".
s is the "substitute" command, see :help :s.
/== is what you want to substitute.
/- is what you want to substitute it with.
Press <CR> (ENTER) to execute the command.
In plain english: "substitute every == with -".
We should get something like:
sum(x[i]*y[i]for i in 1:n) - 15;
sum(z[i]*x[i]for i in 1:n) - 30;
; to >= -s[XXX];
Here is our second command:
:%s/;/ >= -s[XXX];
Where we substitute every ; with a generic >= -s[XXX];. This is also quite simple.
In plain english: "substitute every ; with >= -s[XXX];.
We should get something like:
sum(x[i]*y[i]for i in 1:n) - 15 >= -s[XXX];
sum(z[i]*x[i]for i in 1:n) - 30 >= -s[XXX];
XXX to line number
Here is our third command:
:%s/XXX/\=line('.')
The big change between this command and the other ones is that the replacement part is dynamic. With \=, we use an expression that is evaluated during each execution instead of a fixed string. line('.') is a vimscript function that returns a line number, which is exactly what we want between those brackets.
In plain english: "substitute every XXX with the current line number".
We should get something like:
sum(x[i]*y[i]for i in 1:n) - 15 >= -s[998];
sum(z[i]*x[i]for i in 1:n) - 30 >= -s[999];
Duplication
Here we duplicate each line with a single command:
:g/^/t.|s/-s/ s
:g is the :help :global command.
/^/ matches every line so the following command will be executed on every line.
t is the :help :t command.
. represents the current line.
| separates two commands.
s/-s/ s removes the - before the s on the duplicated line.
In plain english: "mark every line, then copy each marked line below itself, then remove that leading - before the s".
We should get something like:
sum(x[i]*y[i]for i in 1:n) - 15 >= -s[998];
sum(x[i]*y[i]for i in 1:n) - 15 >= s[998];
sum(z[i]*x[i]for i in 1:n) - 30 >= -s[999];
sum(z[i]*x[i]for i in 1:n) - 30 >= s[999];
One last thing
We use one last command to add a line between our blocks:
:g/ s[/put=''
Every line matching s[ is marked.
We use :help :put to append an empty line.
In plain english: "put an empty line after each line with s[".
We should get something like:
sum(x[i]*y[i]for i in 1:n) - 15 >= -s[998];
sum(x[i]*y[i]for i in 1:n) - 15 >= s[998];
sum(z[i]*x[i]for i in 1:n) - 30 >= -s[999];
sum(z[i]*x[i]for i in 1:n) - 30 >= s[999];
What did we learn?
how to perform simple substitutions with :help :substitute,
the notion of :help :range,
how to use an expression in the replacement part (the usual gateway drug to vimscript) with :help sub-replace-\=,
how to execute arbitrary commands on lines matching a specific pattern with :help :global,
how to copy a given line to a given address with :help :t,
how to put some text, here an empty string, below the current line with :help :put,
how to separate commands with :help :|.
Related
I have a file which is as following
!J INCé0001438823
#1 A LIFESAFER HOLDINGS, INC.é0001509607
#1 ARIZONA DISCOUNT PROPERTIES LLCé0001457512
#1 PAINTBALL CORPé0001433777
$ LLCé0001427189
$AVY, INC.é0001655250
& S MEDIA GROUP LLCé0001447162
I just want to keep the last 10 characters of each line so that it becomes as following:-
0001438823
0001509607
0001457512
0001433777
0001427189
0001655250
:%s/.*\(.\{10\}\)/\1
: ex-commaned
% entire file
s/ substitute
.* anything (greedy)
. followed by any character
\{10\} exactly 10 of them
\( \) put them in a match group
/ replace with
\1 said match group
I would treat this as a shell script problem. Enter the following in vim:
:%! rev|cut -c1-10|rev
The :%! will pipe the entire buffer through the following filter, and then the filter comes straight from here.
for a single line you could use:
$9hd0
$ go to end of line
9h go 9 characters left
d0 delete to beginning of line
Assuming the é character appears only once in a line, and only before your target ten digits, then this would seem to work:
:% s/^.*é//
: command
% all lines
s/ / substitute (i.e., search-and-replace) the stuff between / and /
^ search from beginning of line,
. including any character (wildcard),
* any number of the preceding character,
é finding "é";
// replace with the stuff between / and / (i.e., nothing)
Note that you can type the é character by using ctrl-k e' (control-k, then e, then apostrophe, without spaces). On my system at least, this works in insert mode and when typing the "substitute" command. (To see the list of characters you can invoke with the ctrl-k "digraph" feature, use :dig or :digraph.
I've got a directory with more than 100 000 small files.
I'd like to put them into separate folders.
find mydir/ -type f | sort > tt.txt
vim tt.txt
Now I'd like for each line, to insert a number with this formulae:
(line number) + 50000 + ((line number/1000)*1000)
So the original file is something like:
abainville_55130_55001.htm
abancourt_59268_59001.htm
abbenans_25340_25003.htm
abergement-de-cuisery_71290_71001.htm
And should output:
51001 - abainville_55130_55001.htm
51002 - abancourt_59268_59001.htm
51003 - abbenans_25340_25003.htm
51004 - abergement-de-cuisery_71290_71001.htm
and so on until the 1000th line where
line 998 => 51000 - bannes_53340_53019.htm
line 999 => 51000 - bannieres_81500_81022.htm
line 1000 => 52000 - bannoncourt_55300_55027.htm
line 1001 => 52000 - banos_40500_40024.htm
line 1002 => 52000 - bans_39380_39037.htm
and after that I'll apply a macro to transform it to:
mv bannes_53340_53019.htm 51998/bannes_53340_53019.htm
mv bannieres_81500_81022.htm 51999/bannieres_81500_81022.htm
mv bannoncourt_55300_55027.htm 52000/bannoncourt_55300_55027.htm
mv banos_40500_40024.htm 52001/banos_40500_40024.htm
mv bans_39380_39037.htm 52002/bans_39380_39037.htm
(the last part is easy and not the problem, the problem is about maths).
I know there's something like but I can't find the solution to apply my maths:
:s/^/\=(line('.')+51000)/
You don't have to do the job in two steps, you could generate those "mv..." command lines with vim in one short, if you want to stick to vim.
try this line:
%s#.*$#\="mv ".submatch(0)." ".(line(".")+50000+((line(".")/1000)*1000)) . "/" . submatch(0)#
will generate something like: (if the first line is on line number 1)
mv abainville_55130_55001.htm 50001/abainville_55130_55001.htm
mv abancourt_59268_59001.htm 50002/abancourt_59268_59001.htm
mv abbenans_25340_25003.htm 50003/abbenans_25340_25003.htm
mv abergement-de-cuisery_71290_71001.htm 50004/abergement-de-cuisery_71290_71001.htm
You were nearly there. You can use the following command:
:%s/^/\=line('.')+51000 . " - "/
where % applies it to the whole file and . does string concatenation to get your spaced hyphen.
Edit:
In order to use / as part of the expression, you can change the delimiter character (see :help E146). For example, the following generates 51000 for 1000 lines, then 52000 for the next 1000 lines, and so on:
:s!^!\=50000 + (line('.') / 1000) * 1000 . " - "!
Is it possible to substitute a regular expression with a randomly generated number in Vim ? The (random) number to be replaced should be different for each pattern that matches the regular expression. Here's an example of what I need.
Input File:
<a>XYZ</a>
<a>XYZ</a>
<a>XYZ</a>
<a>XYZ</a>
After substituting XYZ with random numbers, the output could be:
<a>599</a>
<a>14253</a>
<a>1718</a>
<a>3064</a>
If you don't mind a little perl in your vim, you can use
:%! perl -pne 's/XYZ/int(rand 1000)/ge'
Edit: updated to allow unlimited substitutions on a given line, per suggestion by #hobbes3, so
XYZ XYZ
XYZ XYZ XYZ
XYZ XYZ XYZ XYZ XYZ XYZ
XYZ XYZ
Becomes something like
86 988
677 477 394
199 821 193 649 502 471
732 208
Try this: put the below code to a buffer then source it (:source %).
let rnd = localtime() % 0x10000
function! Random()
let g:rnd = (g:rnd * 31421 + 6927) % 0x10000
return g:rnd
endfun
function! Choose(n) " 0 n within
return (Random() * a:n) / 0x10000
endfun
Then you can do:
:s_\(<a>\).*\(</a>\)_\1\=Choose(line('.')*100).\2_
I have to remove quotes from a relatively large text file. I have looked at the questions that may have matched this one, yet I am still not able to remove the quotes from specific lines from the text file.
This is what a short segment from the file looks like:
1 - "C1".
E #1 - "C1".
2 - "C2".
1
2
E #2 - "C2".
I would like to have 1-"C1". be replaced by c1, and E #1 - "C1" be replaced with E c1. I tried replacing these in python, but I get an error because of the double "'s.
I tried:
input=open('file.text', 'r')
output=open(newfile.txt','w')
clean==input.read().replace("1 - "C1".","c1").replace("E #1 - "C1"."," E c1")
output.write(clean)
I have with sed:
sed 's\"//g'<'newfile.txt'>'outfile.txt'.
But yet another syntax error.
If you're sure you want literal replacement, you just need to escape the quotes or use single quotes: replace('1 - "C1".', "c1") and so on (and to correct a couple of syntax-breaking typos). This, however, will only work on the first line, so there's no point in reading the whole file then. To do a smarter job, you could use re.sub:
with open('file.text') as input, open('newfile.txt','w') as output:
for line in input:
line = re.sub(r'^(?P<num>\d+) - "C(?P=num)".$',
lambda m: 'c' + m.groups()[0], line)
line = re.sub(r'^E #(?P<num>\d+) - "C(?P=num)".$',
lambda m: 'E c' + m.groups()[0], line)
output.write(line)
In the sed command you seem to try to simply remove the quotes:
sed 's/"//g' newfile.txt > outfile.txt
I have my source code for copy operators written as follows.
foo = rhs.foo;
foobar = rhs.foobar;
bar = rhs.bar;
toto = rhs.toto;
I'd like to line things up as follows (more human readable, isn't it?).
foo = rhs.foo;
foobar = rhs.foobar;
bar = rhs.bar;
toto = rhs.toto;
Is there a VIM magic insert-up-to-column-N, or something like that that would allow me to line things up using a couple of keystrokes per line?
The other answers here are great, especially #nelstrom's comment for Tabular.vim and his excellent screencast.
But if I were feeling too lazy to install any Vim plugins, yet somehow willing to use Vim macros, I'd use macros.
The algorithm:
For each line,
Add tons of spaces before the symbol =
Go to the column you want to align to
Delete all text up to =, thereby shifting the = into the spot you want.
For your example,
foo = rhs.foo;
foobar = rhs.foobar;
bar = rhs.bar;
toto = rhs.toto;
Position the cursor anywhere on the first line and record the macro for that line by typing, in normal mode:
qa0f=100i <Esc>8|dwjq
Which translates to:
qa -- Record a macro in hotkey a
0 -- Go to the beginning of the line
f= -- Go to the first equals sign
100i <Esc> -- (There's a single space after the i, and the <Esc> means press escape, don't type "<Esc>".) Insert 100 spaces
8| -- Go to the 8th column (sorry, you'll have to manually figure out which column to align to)
dw -- Delete until the next non-space character
j -- Go to the next line
q -- Stop recording.
Then run the macro stored at hotkey a, 3 times (for the 3 remaining lines), by putting the cursor on the second line and pressing:
3#a
If you are using a unix-like environment, you can use the command line tool column. Mark your lines using visual mode, then:
:'<,'>!column -t
This pastes the selected text into the stdin of the command after '<,'>!. Note that '<,'>! is inserted automatically when you hit : in visual mode.
There is a nice plugin which does exactly that and more, called Align.vim
For you case, you would need to select your expression and then type :Align =. It will align everything, using = as a separator and reference.
(There is a lots of options to align, left, right, cyclically, etc)
You can also check Tabular.vim which provides similar features. See the screencast there for a demo.
A quick, simple way to proceed is to add X spaces and then delete back to column X. For example, if X=40, type
40a<Space><Esc>d40|
An alternative solution is to perform two consecutive substitutions:
%s/=/ =/
%s/\%>7c *//
The trick is the column pattern \%>7c that matches white spaces * only after the 7th column. Here foobar is the longest variable name with 6 characters so we need 7 in the regex.
We can use these two functions that I described in the below path for the same scenario : https://stackoverflow.com/a/32478708/3146151
simply put those two functions in your .vimrc or .gvimrc and call the functions as normal function call in your editor whenever you want.
The functions I have posted it here : https://github.com/imbichie/vim-vimrc-/blob/master/MCCB_MCCE.vim
We need to call this function in vim editor and give the Number of Occurrence of the Character or Space that you wants to move and the character inside the '' and the column number.
The number of occurrence can be from the starting of each line (MCCB function) or can be at the end of each line (MCCE function).
for the above example mentioned in the question we can use the MCCB function and the character we can use '=', so the usage will be like this in the vim editor.
:1,4call MCCB(1,'=',8)
So this will move the first = sign to the 8th column from line number 1 to 4.
These are the functions :
" MCCB - Move the Character to the Column from the Begin of line
" This is a function for Moving the specified Character
" in a given range of lines to a the specified Column from the Begin of the line
" NOTE 1 :- If the specified character and the first character of the line are same
" then the number of Occurance (num_occr) will be one less than the actual
" NOTE 2 :- Maximum space between the specified character with in the range
" of lines should be less than or equal to 80, if we need more than 80
" then we need to insert more spaces by increasing the value 80 in the
" "nmap s 80i <ESC>" line inside the function
" Usage :- in command mode do it like below
" Eg 1:- :5,11call MCCB(1, '=', 8)
" The above command will move the 1st Occurance from the begin of Character =
" to the 8th Column of the lines from 5 to 11
" Eg 2 :- :7,10call MCCB(2, '+', 12)
" The above command will move the 2nd Occurance of Character = to the 12th
" Column of the lines from 7 to 10
function! MCCB (num_occr, mv_char, col_num) range
if (a:firstline <= a:lastline)
nmap s 80i <ESC>
let line_num = a:firstline
while line_num <= a:lastline
execute "normal " . line_num . "G0" . a:num_occr . "f" . a:mv_char . "s" . a:col_num . "|dw"
let line_num = line_num + 1
endwhile
nunmap s
else
execute printf('ERROR : Start line %d is higher thatn End line %d, a:firstline, a:lastline)
endif
endfunction
" MCCE - Move the Character to the Column from the End of line
" This is a function for Moving the specified Character
" in a given range of lines to a the specified Column from the End of the line
" NOTE 1 :- If the specified character and the last character of the line are same
" then the number of Occurance (num_occr) will be one less than the actual
" NOTE 2 :- Maximum space between the specified character with in the range
" of lines should be less than or equal to 80, if we need more than 80
" then we need to insert more spaces by increasing the value 80 in the
" "nmap s 80i <ESC>" line inside the function
" Usage :- in command mode do it like below
" Eg 1:- :5,11call MCCE(1, ';', 20)
" The above command will move the 1st Occurance from the End of Character ;
" to the 20th Column of the lines from 5 to 11
" Eg 2 :- :7,10call MCCE(5, 'i', 26)
" The above command will move the 5th Occurance from the End of Character i
" to the 26th Column of the lines from 7 to 10
function! MCCE (num_occr, mv_char, col_num) range
if (a:firstline <= a:lastline)
nmap s 80i <ESC>
let line_num = a:firstline
while line_num <= a:lastline
execute "normal " . line_num . "G$" . a:num_occr . "F" . a:mv_char . "s" . a:col_num . "|dw"
let line_num = line_num + 1
endwhile
nunmap s
else
execute printf('ERROR : Start line %d is higher thatn End line %d, a:firstline, a:lastline)
endif
endfunction
I know this is old but I thought #talklittle had the right idea, the answer just got verbose. A quicker way is to insert spaces after the = and then remove all spaces after the 10th column like this:
:1,4 s/^\(.*=\) *\(.*\)$/\1 \2/
:1,4 s/^\(.\{10\}\) *\(.*\)$/\1\2/