DEV Community

SÃĐrgio AraÚjo
SÃĐrgio AraÚjo

Posted on Edited on

Combining awk and vim to get real power

Foreword

After the initial post of this little, "how to", @Sundeep commented:

why not use awk itself to insert the snippet? since you are using gawk, you can also use -i inplace if you want inplace editing

I don't remember exactly, but I think you can use silent to avoid having to use Enter manually, check these: ...

I will explain the scenario, my old solution and then the gawk solution

The Scenario

I have about 500 markdown files on my personal vimwiki, for years I have been making notes to help myself and others through Linux/vim commands. Recently I started using pandoc to convert my markdown files to html and LaTeX.

using vim + gawk

I have two mappings on my ~/.config/init.vim that makes it easy to convert my current documment to html and pdf. During the process of convertion pandoc warns about the lack of the title, and this brought me here.

if !exists('*PandocMappings')
    function! PandocMappings()
        " NOTE: <F16> equals to Shift-F4
        noremap <F4> :! pandoc '%:p' -o /tmp/'%:p:t'.html --template=easy_template.html --toc && firefox /tmp/'%:p:t'.html &<CR><CR>
        noremap <F16> :! pandoc '%:p' -o /tmp/'%:p:t'.pdf --pdf-engine=pdflatex --toc && evince /tmp/'%:p:t'.pdf &<CR><CR>
    endfun
endif

augroup my_markdown
    autocmd!
    autocmd FileType markdown call PandocMappings()
augroup END
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

My markdown files lack metadata, something like this:

--------
file: the file name
lang: pt-BR
author: Sergio Araujo
date: abr 03, 2019
created: abr 03, 2019
abstrac: This aims...
--------
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

After adding manually the metadata in about eight of them I realized that I had better tools and the knowledge (including web search) to solve my problem. The metadata should start at first line.

Finding out the files with no metadata

Using awk, more specificly gnu awk "gwak" printing the list of files who have the metadata is simple

gawk '(FNR==1 && $1 ~ /^---/) {print FILENAME}' *.md

FNR==1  ...................... the first line equals to the first register
&& ........................... it allows us to test a second condition
~  ........................... match regular expression
/^---/ ....................... the regular expression itself
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

The command above showed me the files I already had inserted the metadata, inverting the regex is very simple, just use exclamation. So the list of files that did not had the metatada can be printed with:

gawk '(FNR==1 && $1 !~ /^---/) {print FILENAME}' *.md
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Combining the awk result with vim

Using the shell Command Substitution we can deliver to vim the list we wanted.

vim $(gawk '(FNR==1 && $1 !~ /^---/) {print FILENAME}' *.md)
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

The vim/neovim argdo command

When you open vim with something like:

vim *.md
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

All the opened files are in the arglist, so we can use something like:

:argdo command
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

A UltiSnippet snippet to insert the header

snippet headermd "markdown header" w
--------
file: `!p snip.rv = snip.fn`
lang: pt-BR
author: `!v g:snips_author`
date: `!v strftime("%b %d, %Y")`
created: `!v strftime("%b %d, %Y")`
--------
endsnippet
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

the vim function that can be called by argdo to inserts the snippet

fun! InsertHeaderMd()
    0pu_
    exe "normal! iheadermd\<C-r>=UltiSnips#ExpandSnippet()\<CR>"
endfun
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
0pu_  ............................ On the line zero insert black hole register (blank line)
exe normal! ...................... normal command
iheadermd ........................ enters insert mode and types headermd
\<C-r>= .......................... expression register
UltiSnips#ExpandSnippet()\<CR> ... the UltiSnippet trigger to call the snippet
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Finishing up everything

We do not even have put the function (just the snippet) on the configuration files, you can copy the function to the clipboard and type this on vim:

:@+
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Now with all files opened you can do:

:silent argdo call InsertHeaderMd() 
:silent argdo update
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Thanks to @Sundeep I added the silent option which allowed me run the command without hiting Enter a bunch of times. And he also suggested awk as a solution

Using a vim macro with argdo

Opening the files without metadata

vim $(gawk '(FNR==1 && $1 !~ /^---/) {print FILENAME}' *.md)
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Creating a vim macro

:let @a=''
:let @a="ggO---\<Return>title: \<C-r>=expand('%:t')\<CR>\<Return>author: Sergio AraÚjo\<Return>abstract:\<Return>---\<Esc>"
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Explanation

let @a='' ............................ delete any marcro 'a'
ggO .................................. got to the first line and open a line above
---\<Return> ......................... insert three dashes and a new line
\<C-r>=  ............................. starts a expression register
expand('%:t') ........................ expands the filename "t = tail" 
\<Esc> ............................... goes back to the normal mode
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Running the argdo command plus a macro

:set hidden ............................ allows to go to the next file without saving

:silent argdo execute "normal! @a" | update 

:silent ................................. does not show any messages
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

The gawk solution

This solution came from a @Sundeep comment

  var=$(date "+%b %m, %Y")
  awk -i inplace -v date="$var" 'BEGINFILE {print "--\nfile: "FILENAME"\ndate: "date"\nabstract: \n---"}; (FNR==1 && $1 !~ /^---/) {print}' *.md 
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Job done!

Explaining the awk long command...

-i inplace ..................... makes possible edit files directly in awk
-v date="$var" ................. captures the content of var=$(date "+%b %m, %Y")
BEGINFILE ...................... insets some strings "between quotes"
FILENAME ....................... gawk variable that returns the file name
FNR==1  ........................ the first line equals to the first register
&& ............................. it allows us to test a second condition
~  ............................. match regular expression
/^---/ ......................... the regular expression itself
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

The next step will be using a variable using shell Here Documents. If you guys can help me with this I would thank you a lot!

Could it be done with sed?

I know that sed can also insert a header in a bunch of files, but I should have write a shell script to get each file name or use -V sed option. The solution I figured out was good for my problem, and it also inspired me enough to share this ideas with you. Feel free to comment and suggest alternatives or improvements. (Please fix my typing, I am not a native English speaker).

References:

Top comments (3)

🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
learnbyexample profile image
Sundeep •

why not use awk itself to insert the snippet? since you are using gawk, you can also use -i inplace if you want inplace editing

I don't remember exactly, but I think you can use silent to avoid having to use Enter manually, check these:

🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
voyeg3r profile image
SÃĐrgio AraÚjo •

Thanks @Sundeep for your tips! I will update my post. But actually the real purpuse of it was reached, since many people will learn more about awk, vim, and of course, I will learn more with your great suggestions.

🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: voyegr/combining awk and vim to get real power mf - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
voyeg3r profile image
SÃĐrgio AraÚjo • • Edited

Hi @Sundeep, I have made some changes on my article, now it shows a pure awk solution, a vim macro solution + awk. Both brings us more ideas for the future needs.