how i can create macro/script that would put information i need for code in following syntax
switch n
case 1
country=af
coutry_word=Afghanistan
country_flag=https://imgur.com/yRbLG3B
country_code_number=+324
break;
case 2
...
I'm doing this in notepad++, i was able to automate create everything except adding unique information for all these variables. (there are 240 of these case -s , so it means i would have to manually put 960 times into each of one of these variables)
But i already have in order information that needs to be putted into variables, and that (for easier scripting) is put right into same file as this code. I just need to add them correspondly (and list is sorted, so it just need to be put into variables in that sorted way)
Here's what i mean
https://imgur.com/y9G1Nim
https://imgur.com/xmU8rwh
https://imgur.com/wfcLo9a
https://imgur.com/wdRUl1Z
https://imgur.com/vxnPnl3
https://imgur.com/uNhaTYi
...
Afghanistan
Åland Islands
Algeria
Andorra
Antarctica
Argentina
Australia
...
TPE
AFG
ALB
ALG
ASA
AND
ANG
AIA
ROS
ATG
ARG
...
+123
+458
+554
+588
+585
+584
...
and they are within same file as this written code
with macros in notepad++
i managed to get only this much (with NP++ macros)
switch n
case 1
country=
coutry_word=
country_flag=
country_code_number=
break;
case 2
...
so only that remains is to put these ordered text into these variables. i tried with macro, but it doesn't continue to the end of file.
If NP++ can't, i'm also comfortable in Vim
Here is how I would approach the problem, in Vim, on my Unix-like system:
Clean up the data so that there is no leading or trailing whitespace:
:%left
:keeppattern [range]s/\s\+$//
Select each block of data and write it into its own file, with the appropriate prefix:
Place the cursor on the "country flags" block.
Visually select the block with vip.
Write the block to a file with :'<,'>!sed 's/^/country_flag=/' >> country_flag.txt.
Repeat for the other blocks in their corresponding files:
country_word.txt
country.txt
country_code_number.txt
At this point, we should have the following:
$ cat country.txt
country=TPE
country=AFG
country=ALB
country=ALG
country=ASA
country=AND
$ cat country_code_number.txt
country_code_number=+123
country_code_number=+458
country_code_number=+554
country_code_number=+588
country_code_number=+585
country_code_number=+584
$ cat country_flag.txt
country_flag=https://imgur.com/y9G1Nim
country_flag=https://imgur.com/xmU8rwh
country_flag=https://imgur.com/wfcLo9a
country_flag=https://imgur.com/wdRUl1Z
country_flag=https://imgur.com/vxnPnl3
country_flag=https://imgur.com/uNhaTYi
$ cat country_word.txt
country_word=Afghanistan
country_word=Åland Islands
country_word=Algeria
country_word=Andorra
country_word=Antarctica
country_word=Argentina
Interleave those files and insert the result in the buffer:
:read !paste -d '\n' country*
Which should give us the following:
country=TPE
country_code_number=+123
country_flag=https://imgur.com/y9G1Nim
country_word=Afghanistan
country=AFG
country_code_number=+458
country_flag=https://imgur.com/xmU8rwh
country_word=Åland Islands
country=ALB
country_code_number=+554
country_flag=https://imgur.com/wfcLo9a
country_word=Algeria
country=ALG
country_code_number=+588
country_flag=https://imgur.com/wdRUl1Z
country_word=Andorra
country=ASA
country_code_number=+585
country_flag=https://imgur.com/vxnPnl3
country_word=Antarctica
country=AND
country_code_number=+584
country_flag=https://imgur.com/uNhaTYi
country_word=Argentina
All that is left is really to turn all that into case blocks.
We could try to do it with a nifty one-liner but it is a lot easier and time-efficient to split the task into simpler subtasks.
Add case n above each block as well as an empty line:
:let i = 1 | g/country=/put!=['','case '.i] | let i += 1
Which gives us the following:
case 1
country=TPE
country_code_number=+123
country_flag=https://imgur.com/y9G1Nim
country_word=Afghanistan
case 2
country=AFG
country_code_number=+458
country_flag=https://imgur.com/xmU8rwh
country_word=Åland Islands
case 3
country=ALB
country_code_number=+554
country_flag=https://imgur.com/wfcLo9a
country_word=Algeria
case 4
country=ALG
country_code_number=+588
country_flag=https://imgur.com/wdRUl1Z
country_word=Andorra
case 5
country=ASA
country_code_number=+585
country_flag=https://imgur.com/vxnPnl3
country_word=Antarctica
case 6
country=AND
country_code_number=+584
country_flag=https://imgur.com/uNhaTYi
country_word=Argentina
Add the break statement at the end of each block:
:g/country_word/put='break;'
which gives us the following:
case 1
country=TPE
country_code_number=+123
country_flag=https://imgur.com/y9G1Nim
country_word=Afghanistan
break;
case 2
country=AFG
country_code_number=+458
country_flag=https://imgur.com/xmU8rwh
country_word=Åland Islands
break;
case 3
country=ALB
country_code_number=+554
country_flag=https://imgur.com/wfcLo9a
country_word=Algeria
break;
case 4
country=ALG
country_code_number=+588
country_flag=https://imgur.com/wdRUl1Z
country_word=Andorra
break;
case 5
country=ASA
country_code_number=+585
country_flag=https://imgur.com/vxnPnl3
country_word=Antarctica
break;
case 6
country=AND
country_code_number=+584
country_flag=https://imgur.com/uNhaTYi
country_word=Argentina
break;
Last step, indent the country* lines:
:g/^country/>
Which should give us the following:
case 1
country=TPE
country_code_number=+123
country_flag=https://imgur.com/y9G1Nim
country_word=Afghanistan
break;
case 2
country=AFG
country_code_number=+458
country_flag=https://imgur.com/xmU8rwh
country_word=Åland Islands
break;
case 3
country=ALB
country_code_number=+554
country_flag=https://imgur.com/wfcLo9a
country_word=Algeria
break;
case 4
country=ALG
country_code_number=+588
country_flag=https://imgur.com/wdRUl1Z
country_word=Andorra
break;
case 5
country=ASA
country_code_number=+585
country_flag=https://imgur.com/vxnPnl3
country_word=Antarctica
break;
case 6
country=AND
country_code_number=+584
country_flag=https://imgur.com/uNhaTYi
country_word=Argentina
break;
All the commands put together:
:left
:keepp [range]s/\s\+$
vip
:'<,'>!sed 's/^/country_flag=/' >> country_flag.txt
(move cursor to next block)
vip
:<Up>
<C-f>
s/flag/word/g
<CR>
(move cursor to next block)
vip
:<Up>
<C-f>
s/_word//g
<CR>
(move cursor to next block)
vip
:<Up>
<C-f>
s/country/&_code_number/g
<CR>
:read !paste -d '\n' country*
:let i = 1 | g/country=/put!=['','case '.i] | let i += 1
:g/country_word/put='break;'
:g/^country/>
Now, my non-vimmer self from 2009 would probably have brushed this off as too weird or complicated but, for my vimmer self of early 2022, well… it all flows pretty naturally (except for the paste syntax that I always have to look up) so I guess it's all a matter of experience with one's tools. It took a lot longer for me to verify the result of my commands and write that answer than it took to formulate the answer in my head or than it would have taken to actually do it without having to write an answer at the same time. We are talking about a two-three minutes affair, tops.
Reference:
:help :left
:help :keeppattern
:help ip
:help :range
:help :!
:help command-line-window
:help :read
:help :let
:help :global
:help :put
:help :>
$ man sed
$ man paste
Related
I'm trying to select all email addresses and move them to the end of the line using sed. I've successfully figured out how to select the email addresses but can do nothing more than append $ to the end - is there a way to combine the & (to paste back in the selected words) and $ (to go to end of line) operators to accomplish what I'm looking to do? Or should I look for a different method?
-bash-3.2$ sed "s/[a-z]*#*\.*\.[a-z].[a-z]/& $/" address
Xiao Li, lxiao#unc.edu $, 6705462234, Jackson, NC 764
Elizi Moe, emoe#ncsu.edu $, 5208534566, Tempe, AZ 85282
Ma Ta, mta#yahoo.com $, 4345667345, Austin, TX 91030
Diana Cheng, dcheng#asu.edu $, 5203456789, Matitsi, WY 4587
Jackson Five, jfive#ncsu.edu $, 5206564573, Kyenta, AZ 85483
Adi Srikanth Reddy, sadi1#imap1.asu $.edu, 6578904566, Wyo, WS 67854
Natkin William, wnatkin#imap28.asu $.edu, 8044344528, Richmond, VA 22345
*note: these are false names, addresses & phone numbers.
You can try this sed command:
sed -r "s/^([^,]+)(,[^,]+)(.*)/\1\3\2/g" address
Explanation
$^([^,]+) captures everything from start of line upto but not including the first comma (i.e. the first column) into \1
(,[^,]+) captures the first comma and everything upto but not including the second comma into \2 (i.e. the second column, the email)
(.*) captures the rest into \3
then the columns are swapped using \1\3\2 backreference to the capture groups marked by the parentheses
Similarly you can do this with perl too..
%_Host#User:/home/Gaurava/study/temp> perl -pe 's/^(.+?),(.+?),(.+)$/$1,$3,$2/g' names.txt
Xiao Li, 6705462234, Jackson, NC 764, lxiao#unc.edu $
Elizi Moe, 5208534566, Tempe, AZ 85282, emoe#ncsu.edu $
Ma Ta, 4345667345, Austin, TX 91030, mta#yahoo.com $
Diana Cheng, 5203456789, Matitsi, WY 4587, dcheng#asu.edu $
Jackson Five, 5206564573, Kyenta, AZ 85483, jfive#ncsu.edu $
Adi Srikanth Reddy, 6578904566, Wyo, WS 67854, sadi1#imap1.asu $.edu
Natkin William, 8044344528, Richmond, VA 22345, wnatkin#imap28.asu $.edu
%_Host#User:/home/Gaurava/study/temp>
Here you can see an output of "cat tcl.log":
Discovered serial numbers for slot 1 olt 1:
sernoID Vendor Serial Number sernoID Vendor Serial Number
5 ZNTS 032902A6
And that's how it looks in VIM:
^MDiscovered serial numbers for slot 1 olt 1:
^MsernoID Vendor Serial Number sernoID Vendor Serial Number
^M<SPACE> for next page, <CR> for next line, A for all, Q to quit^H ^H^H ^H^...
5 ZNTS 032902A6
I don't mind the ^M and ^H characters, I know how to get rid of them. The problem is that for some reason my C++ program (unlike cat) is seeing the line starting with "< SPACE >". What can I do about it? I'm using the fstream library to read the log file and I want it to ignore the line I mentioned. I tried to do something like this:
std::ofstream logFinal("logFinal");
std::ifstream log("tcl.log");
std::string temp;
while (std::getline(log, temp)){
if (temp.find("SPACE") != std::string::npos){
temp = "";
}
logFinal << temp << std::endl;
}
But for some reason it doesn't find any "SPACE" in the temp variable. It looks like the "< SPACE >" is some kind of a special character of which I've never heard about.
You're obtaining that log file from/via some sort of program that does paging. (It could be buried inside things; these things happen.) That paging program prints a message like this at the end of a page:
<SPACE> for next page, <CR> for next line, A for all, Q to quit
The <SPACE> is just part of some message with human-readable text; it's seven very ordinary characters. HOWEVER, the ^H that follow it are more interesting, as they're really backspace characters; it's where the preceding characters are deleted again to make way for the next line of real output.
The easiest way (assuming you're on — or have easy access to — a Unix/Linux system) is to feed that log file through col -b (the col program with the -b option, to do backspace elimination). Check out this little cut-n-paste from a shell session:
bash$ echo -e 'abc\b\b\bdef'
def
bash$ echo -e 'abc\b\b\bdef' | od -c
0000000 a b c \b \b \b d e f \n
0000012
bash$ echo -e 'abc\b\b\bdef' | col -b | od -c
0000000 d e f \n
0000004
(The \b should be the same as ^H in your log file.)
I have a file that contains lines as follows:
one one
one one
two two two
one one
three three
one one
three three
four
I want to remove all occurrences of the duplicate lines from the file and leave only the non-duplicate lines. So, in the example above, the result should be:
two two two
four
I saw this answer to a similar looking question. I tried to modify the ex one-liner as given below:
:syn clear Repeat | g/^\(.*\)\n\ze\%(.*\n\)*\1$/exe 'syn match Repeat "^' . escape(getline ('.'), '".\^$*[]') . '$"' | d
But it does not remove all occurrences of the duplicate lines, it removes only some occurrences.
How can I do this in vim? or specifically How can I do this with ex in vim?
To clarify, I am not looking for sort u.
If you have access to UNIX-style commands, you could do:
:%!sort | uniq -u
The -u option to the uniq command performs the task you require. From the uniq command's help text:
-u, --unique
only print unique lines
I should note however that this answer assumes that you don't mind that the output doesn't match any sort order that your input file might have already.
if you are on linux box with awk available, this line works for your needs:
:%!awk '{a[$0]++}END{for(x in a)if(a[x]==1)print x}'
Assuming you are on an UNIX derivative, the command below should do what you want:
:sort | %!uniq -u
uniq only works on sorted lines so we must sort them first with Vim's buit-in :sort command to save some typing (it works on the whole buffer by default so we don't need to pass it a range and it's a built-in command so we don't need the !).
Then we filter the whole buffer through uniq -u.
My PatternsOnText plugin version 1.30 now has a
:DeleteAllDuplicateLinesIgnoring
command. Without any arguments, it'll work as outlined in your question.
It does not preserve the order of the remaining lines, but this seems to work:
:sort|%s/^\(.*\)\n\%(\1\n\)\+//
(This version is #Peter Rincker's idea, with a little correction from me.) On vim 7.3, the following even shorter version works:
:sort | %s/^\(.*\n\)\1\+//
Unfortunately, due to differences between the regular-expression engines, this no longer works in vim 7.4 (including patches 1-52).
Taking the code from here and modifying it to delete the lines instead of highlighting them, you'll get this:
function! DeleteDuplicateLines() range
let lineCounts = {}
let lineNum = a:firstline
while lineNum <= a:lastline
let lineText = getline(lineNum)
if lineText != ""
if has_key(lineCounts, lineText)
execute lineNum . 'delete _'
if lineCounts[lineText] > 0
execute lineCounts[lineText] . 'delete _'
let lineCounts[lineText] = 0
let lineNum -= 1
endif
else
let lineCounts[lineText] = lineNum
let lineNum += 1
endif
else
let lineNum += 1
endif
endwhile
endfunction
command! -range=% DeleteDuplicateLines <line1>,<line2>call DeleteDuplicateLines()
This is not any simpler than #Ingo Karkat's answer, but it is a little more flexible. Like that answer, this leaves the remaining lines in the original order.
function! RepeatedLines(...)
let first = a:0 ? a:1 : 1
let last = (a:0 > 1) ? a:2 : line('$')
let lines = []
for line in range(first, last - 1)
if index(lines, line) != -1
continue
endif
let newlines = []
let text = escape(getline(line), '\')
execute 'silent' (line + 1) ',' last
\ 'g/\V' . text . '/call add(newlines, line("."))'
if !empty(newlines)
call add(lines, line)
call extend(lines, newlines)
endif
endfor
return sort(lines)
endfun
:for x in reverse(RepeatedLines()) | execute x 'd' | endfor
A few notes:
My function accepts arguments instead of handling a range. It defaults to the entire buffer.
This illustrates some of the functions for manipulating lists. :help list-functions
I use /\V (very no magic) so the only character I need to escape in a search pattern is the backslash itself. :help /\V
Add line number so that you can restore the order before sort
:%s/^/=printf("%d ", line("."))/g
sort
:sort /^\d+/
Remove duplicate lines
:%s/^(\d+ )(.*)\n(\d+ \2\n)+//g
Restore order
:sort
Remove line number added in #1
:%s/^\d+ //g
please use perl ,perl can do it easily !
use strict;use warnings;use diagnostics;
#read input file
open(File1,'<input.txt') or die "can not open file:$!\n";my #data1=<File1>;close(File1);
#save row and count number of row in hash
my %rownum;
foreach my $line1 (#data1)
{
if (exists($rownum{$line1}))
{
$rownum{$line1}++;
}
else
{
$rownum{$line1}=1;
}
}
#if number of row in hash =1 print it
open(File2,'>output.txt') or die "can not open file:$!\n";
foreach my $line1 (#data1)
{
if($rownum{$line1}==1)
{
print File2 $line1;
}
}
close(File2);
By outside, I want solutions that does not use Vim's scripting hacks but try to reuse certain basic *ix tools. Inside Vim stuff asks for solutions to get the column-increment with inside stuff such as scripting.
1 1
1 2
1 3
1 ---> 4
1 5
1 6
. .
. .
Vim has a script that does column-vise incrementing, VisIncr. It has gathered about 50/50 ups and down, perhaps tasting a bit reinventing-the-wheel. How do you column-increment stuff in Vim without using such script? Then the other question is, how do you column-increment stuff without/outside Vim?
Most elegant, reusable and preferably-small wins the race!
I don't see a need for a script, a simple macro would do
"a yyp^Ayy
then play it, or map to play it.
Of course, there is always the possibility that I misunderstood the question entirely...
The optimal choice of a technique highly depends on the actual circumstances
of the transformation. There are at least two points variations affecting
implementation:
Whether the lines to operate on are the only ones in a file? If not,
is the range of lines defined by context (i.e. it separated by blank
lines, like a paragraph) or is it arbitrary and should be specified by
user?
Are those lines already contain numbers that should be changed or is
it necessary to insert new ones leaving the text on the lines in tact?
Since there is no information to answer these questions, below we will try to
construct a flexible solution.
A general solution is a substitution operating on the beginnings of the lines
in the range specified by the user. Visual mode is probably the simplest way
of selecting an arbitrary range of lines, so we assume here that boundaries of
the range are defined by the visual selection.
:'<,'>s/^\d\+/\=line(".")-line("''")+1/
If it is necessary to number every line in a buffer, the command can be
simplified as follows.
:%s/^\d\+/\=line('.')/
In any case, if the number should be merely inserted at the beginnings of the
lines (without modifying the ones that already exist), one can change the
pattern from ^\d\+ to ^, and optionally add a separator:
:'<,'>s/^\d\+/\=(line(".")-line("''")+1).' '/
or
:%s/^/\=line('.').' '/
respectively.
For a solution based on command-line tools, one can consider using stream
editors like Sed or text extraction and reporting tools like AWK.
To number each of the lines in a file using Sed, run the commands
$ sed = filename | sed 'N;s/\n/ /'
In order to do the same in AWK, use the command
$ awk '{print NR " " $0}' filename
which could be easily modfied to limit numbering to a particular range of lines
satisfying a certain condition. For example, the following command numbers the
lines two through eight.
$ awk '{print (2<=NR && NR<=8 ? ++n " " : "") $0}' filename
Having an interest in how commands similar to those from the script linked in
the question statement are implemented, one can use the following command as
a reference.
vnoremap <leader>i :call EnumVisualBlock()<cr>
function! EnumVisualBlock() range
if visualmode() != "\<c-v>"
return
endif
let [l, r] = [virtcol("'<"), virtcol("'>")]
let [l, r] = [min([l, r]), max([l, r])]
let start = matchstr(getline("'<"), '^\d\+', col("'<")-1)
let off = start - line("'<")
let w = max(map([start, line("'>") + off], 'len("".v:val)'))
exe "'<,'>" 's/\%'.l.'v.*\%<'.(r+1).'v./'.
\ '\=printf("%'.w.'d",line(".")+off).repeat(" ",r-l+1-w)'
endfunction
If you want change 1 1 1 1 ... to 1 2 3 4 .... (Those numbers should be on different lines.)
:let i=1 | g/1/s//\=i/g | let i+=1
If some of 1 1 1 1 ... are in the same line:
:let g:i = 0
:func! Inc()
: let g:i+=1
: return g:i
:endfun
:%s/1/\=Inc()/g
Is there a command to determine length of a longest line in vim? And to append that length at the beginning of the file?
Gnu's wc command has a -L --max-line-length option which prints out the max line length of the file. See the gnu man wc. The freebsd wc also has -L, but not --max-line-length, see freebsd man wc.
How to use these from vim? The command:
:%!wc -L
Will filter the open file through wc -L and make the file's contents the maximum line length.
To retain the file contents and put the maximum line length on the first line do:
:%yank
:%!wc -L
:put
Instead of using wc, Find length of longest line - awk bash describes how to use awk to find the length of the longest line.
Ok, now for a pure Vim solution. I'm somewhat new to scripting, but here goes. What follows is based on the FilterLongestLineLength function from textfilter.
function! PrependLongestLineLength ( )
let maxlength = 0
let linenumber = 1
while linenumber <= line("$")
exe ":".linenumber
let linelength = virtcol("$")
if maxlength < linelength
let maxlength = linelength
endif
let linenumber = linenumber+1
endwhile
exe ':0'
exe 'normal O'
exe 'normal 0C'.maxlength
endfunction
command PrependLongestLineLength call PrependLongestLineLength()
Put this code in a .vim file (or your .vimrc) and :source the file. Then use the new command:
:PrependLongestLineLength
Thanks, figuring this out was fun.
If you work with tabulations expanded, a simple
:0put=max(map(getline(1,'$'), 'len(v:val)'))
is enough.
Otherwise, I guess we will need the following (that you could find as the last example in :h virtcol(), minus the -1):
0put=max(map(range(1, line('$')), "virtcol([v:val, '$'])-1"))
:!wc -L %
rather than
:%!wc -L
To append that length at the beginning of the file:
:0r !wc -L % | cut -d' ' -f1
Here is a simple, hence easily-remembered approach:
select all text: ggVG
substitute each character (.) with "a": :'<,'>s/./a/g
sort, unique: :'<,'>sort u
count the characters in the longest line (if too many characters to easily count, just look at the column position in the Vim status bar)
I applied this to examine Enzyme Commission (EC) numbers, prior to making a PostgreSQL table:
I copied the ec_numbers data to Calc, then took each column in Neovim, replaced each character with "a",
:'<,'>s/./a/g
and then sorted for unique lines
:'<,'>sort u
aaaaaaa
aaaaaaaa
aaaaaaaaa
aaaaaaaaaa
aaaaaaaaaaa
... so the longest EC number entry [x.x.x.x] is 11 char, VARCHAR(11).
Similarly applied to the Accepted Names, we get
aaaaa
aaaaaa
...
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
i.e. the longest name is 147 char: VARCHAR(200) should cover it!
For neovim users:
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local width = #(lines[1])
for _, line in ipairs(lines) do
if #line > width then
width = #line
end
end