VBScript - how to append new line after line specified - text

I need to edit some configuration file by appending new line to existing file, but not at the end of the file but somewhere in the middle instead (at the end of particular section)
# section 1 description
foo1 = bar1
foo2 = bar2
# section 2 description
foo3 = c:\bar.cfg
my_new_line = which_needs_to_be_appended_here
# section 3 description
foo4 = bar4
Should I use search and replace like described here:
http://blogs.technet.com/b/heyscriptingguy/archive/2005/02/08/how-can-i-find-and-replace-text-in-a-text-file.aspx
to find last line of particular section and replace it with: itself + new line character + my_new_line = which_needs_to_be_appended?
OR
maybe there is a simpler or more clever method to do same thing (like finding the last line of particular section and use some method to put my new line right AFTER it)?

As your task is to append a line to a section and your data seems to indicate that sections are separated by two line endings, using Split() on that separator looks like a good strategy that doesn't rely on knowing the last key-value-pair of that section:
Dim sAll : sAll = readAllFromFile("..\data\cfg00.txt")
WScript.Echo sAll
Dim aSects : aSects = Split(sAll, vbCrLf & vbCrLf)
aSects(1) = aSects(1) & vbCrLf & "fooA = added"
sAll = Join(aSects, vbCrLf & vbCrLf)
WScript.Echo "-----------------------"
WScript.Echo sAll
output:
=========================
# section 1 description
foo1 = bar1
foo2 = bar2
# section 2 description
foo3 = c:\bar.cfg
# section 3 description
foo4 = bar4
-----------------------
# section 1 description
foo1 = bar1
foo2 = bar2
# section 2 description
foo3 = c:\bar.cfg
fooA = added
# section 3 description
foo4 = bar4
=========================

Related

Regex for getting multiple words after a delimiter

I have been trying to get the separate groups from the below string using regex in PCRE:
drop = blah blah blah something keep = bar foo nlah aaaa rename = (a=b d=e) obs=4 where = (foo > 45 and bar == 35)
Groups I am trying to make is like:
1. drop = blah blah blah something
2. keep = bar foo nlah aaaa
3. rename = (a=b d=e)
4. obs=4
5. where = (foo > 45 and bar == 35)
I have written a regex using recursion but for some reason recursion is partially working for selecting multiple words after drop like it's selecting just first 3 words (blah blah blah) and not the 4th one. I have looked through various stackoverflow questions and have tried using positive lookahead also but this is the closest I could get to and now I am stuck because I am unable to understand what I am doing wrong.
The regex that I have written: (?i)(drop|keep|where|rename|obs)\s*=\s*((\w+|\d+)(\s+\w+)(?4)|(\((.*?)\)))
Same can be seen here: RegEx Demo.
Any help on this or understanding what I am doing wrong is appreciated.
You could use the newer regex module with DEFINE:
(?(DEFINE)
(?<key>\w+)
(?<sep>\s*=\s*)
(?<value>(?:(?!(?&key)(?&sep))[^()=])+)
(?<par>\((?:[^()]+|(?&par))+\))
)
(?P<k>(?&key))(?&sep)(?P<v>(?:(?&value)|(?&par)))
See a demo on regex101.com.
In Python this could be:
import regex as re
data = """
drop = blah blah blah something keep = bar foo nlah aaaa rename = (a=b d=e) obs=4 where = (foo > 45 and bar == 35)
"""
rx = re.compile(r'''
(?(DEFINE)
(?<key>\w+)
(?<sep>\s*=\s*)
(?<value>(?:(?!(?&key)(?&sep))[^()=])+)
(?<par>\((?:[^()]+|(?&par))+\))
)
(?P<k>(?&key))(?&sep)(?P<v>(?:(?&value)|(?&par)))''', re.X)
result = {m.group('k').strip(): m.group('v').strip()
for m in rx.finditer(data)}
print(result)
And yields
{'drop': 'blah blah blah something', 'keep': 'bar foo nlah aaaa', 'rename': '(a=b d=e)', 'obs': '4', 'where': '(foo > 45 and bar == 35)'}
You can use a branch reset group solution:
(?i)\b(drop|keep|where|rename|obs)\s*=\s*(?|(\w+(?:\s+\w+)*)(?=\s+\w+\s+=|$)|\((.*?)\))
See the PCRE regex demo
Details
(?i) - case insensitive mode on
\b - a word boundary
(drop|keep|where|rename|obs) - Group 1: any of the words in the group
\s*=\s* - a = char enclosed with 0+ whitespace chars
(?| - start of a branch reset group:
(\w+(?:\s+\w+)*) - Group 2: one or more word chars followed with zero or more repetitions of one or more whitespaces and one or more word chars
(?=\s+\w+\s+=|$) - up to one or more whitespaces, one or more word chars, one or more whitespaces, and =, or end of string
| - or
\((.*?)\) - (, then Group 2 capturing any zero or more chars other than line break chars, as few as possible and then )
) - end of the branch reset group.
See Python demo:
import regex
pattern = r"(?i)\b(drop|keep|where|rename|obs)\s*=\s*(?|(\w+(?:\s+\w+)*)(?=\s+\w+\s+=|$)|\((.*?)\))"
text = "drop = blah blah blah something keep = bar foo nlah aaaa rename = (a=b d=e) obs=4 where = (foo > 45 and bar == 35)"
print( [x.group() for x in regex.finditer(pattern, text)] )
# => ['drop = blah blah blah something', 'keep = bar foo nlah aaaa', 'rename = (a=b d=e)', 'obs=4', 'where = (foo > 45 and bar == 35)']
print( regex.findall(pattern, text) )
# => [('drop', 'blah blah blah something'), ('keep', 'bar foo nlah aaaa'), ('rename', 'a=b d=e'), ('obs', '4'), ('where', 'foo > 45 and bar == 35')]

How to take action on the current line of one file based on the next line in a different file

I am trying to learn file handling in Python. I need to create a new file with the below changes.
I have the following Input file:-
Input File:-
Hello World!!!
foo bar qss
> foot
> grass
> flower
> leaf
foo1 bar1 qss1
> dragon
> quiz
foo2 bar2 qsq1
foo3 bar3 qsa2
My Output file should be:-
Hello World!!!
foo bar qss foot grass flower leaf
foo1 bar1 qss1 dragon quiz
foo2 bar2 qsq1
foo3 bar3 qsa2
So, If there is '>' in next line, it should copy the contents to the current line.
I tried to use next() but it takes the file pointer to the next line.
#f1 is read file pointer
#f2 is write file pointer
for myline in f1:
if(next(f1) != '>'):
f2.write("myline[1]+" "+myline[2]+" "+myline[3]+" "+next(f1)[3])
Instead of looking forward, look backward: add a line break to the previous line only if the next line does not start with "> ".
with open("f.txt") as f1, open("g.txt", "w") as f2:
for myline in f1:
myline = myline.strip() # Remove the line break
if myline.startswith("> "):
f2.write(myline[1:])
else:
f2.write("\n" + myline)
with open(r"input.txt") as input:
lines = input.readlines()
new_lines = (''.join(lines).replace('\n> ', ''))
with open(r"output.txt", 'w') as output:
output.writelines(new_lines)
input:
Hello World!!!
foo bar qss
> foot
> grass
> flower
> leaf
foo1 bar1 qss1
> dragon
> quiz
foo2 bar2 qsq1
foo3 bar3 qsa2
output:
Hello World!!!
foo bar qss foot grass flower leaf
foo1 bar1 qss1 dragon quiz
foo2 bar2 qsq1
foo3 bar3 qsa2
Hope it helped

String interpolation

In scala, you easily include the content of a variable inside a string, like this:
val nm = "Arrr"
println(s"my name is , $nm")
Is this possible in nim, and in that case, how?
The strfmt module features some experimental string interpolation:
import strfmt
let nm = "Arrr"
echo interp"my name is $nm"
Adding your own string interpolation is not particularly had, since the standard library already provides most of the necessary pieces:
import macros, parseutils, sequtils
macro i(text: string{lit}): expr =
var nodes: seq[PNimrodNode] = #[]
# Parse string literal into "stuff".
for k, v in text.strVal.interpolatedFragments:
if k == ikStr or k == ikDollar:
nodes.add(newLit(v))
else:
nodes.add(parseExpr("$(" & v & ")"))
# Fold individual nodes into a statement list.
result = newNimNode(nnkStmtList).add(
foldr(nodes, a.infix("&", b)))
const
multiplier = 3
message = i"$multiplier times 2.5 is ${multiplier * 2.5}"
echo message
# --> 3 times 2.5 is 7.5
proc blurb(a: int): string =
result = i"param a ($a) is not a constant"
when isMainModule:
for f in 1..10:
echo f.blurb
The strformat module is now considered the go-to way to do it:
import strformat
let nm = "Arrr"
echo fmt"my name is , {nm}"

vbscript Eval a string to a Variable in a loop?

I am trying to use vbscript's Eval (or maybe I need Execute) to create some variables from the key names from an ini file. The ini file can have unlimited unknown key=val pairs. I need to create a variable based on the key name no matter what.
Ini File contents:
myPath=c:\test
myExe=myapp.exe
....
xxx=123
yyy=abc
My code that reads the ini and returns the key and values to an object
The code I am trying to get working is here:
For each pair in objINI
Eval("pair.key=pair.val")
Next
msgbox myPath
msgbox myExe
But both msgbox's are showing empty
And yes I am sure pair.key and pair.val have the correct values.
Thoughts on what I am missing or if this is even possible?
You need to Execute (an assign statement), not to Eval(uate a boolean expression):
>> n = "Name"
>> v = "Value"
>> WScript.Echo TypeName(Eval("n=v"))
>>
Boolean
>> Execute "n=v"
>> WScript.Echo n
>>
Value
>>
From the docs:
In VBScript, x = y can be interpreted two ways. The first is as an
assignment statement, where the value of y is assigned to x. The
second interpretation is as an expression that tests if x and y have
the same value. If they do, result is True; if they are not, result is
False. The Execute statement always uses the first interpretation,
whereas the Eval method always uses the second.
(This does not mean you should do such things; neither at home, nor at work)
You eval'd the literal code pair.key = pair.value.
That assigns to pair.key.
You want to assign to the value of pair.key – if pair.key is myPath, you want to eval myPath = pair.value.
You can do that by concatenating strings:
Execute(pair.name + " = pair.value")
If you want to read key/value pairs from an INI file you'd be better off storing them in a dictionary. I wrote a function for this some years ago. Basically looks like this:
Function ParseIni(filename)
Set ParseIni = Nothing
Set config = CreateObject("Scripting.Dictionary")
section = ""
Set file = CreateObject("Scripting.FileSystemObject").OpenTextFile(filename)
Do While Not file.AtEndOfStream
line = Trim(Replace(file.ReadLine, vbTab, " "))
If InStr(line, ";") > 0 Then line = Trim(Left(line, InStr(line, ";") - 1))
If line <> "" Then
If Left(line, 1) = "[" And Right(line, 1) = "]" Then
' line is a section name
section = Trim(Mid(line, 2, Len(line) - 2))
If section = "" Then _
WScript.Echo "Parse Error: section name is empty string."
If config.Exists(section) Then _
WScript.Echo "Parse Error: duplicate section name '" & name & "'."
config.Add section, CreateObject("Scripting.Dictionary")
ElseIf InStr(line, "=") > 0 Then
' line is a parameter line
If section = "" And Not config.Exists(section) Then _
config.Add section, CreateObject("Scripting.Dictionary")
param = Split(line, "=", 2)
param(0) = Trim(param(0))
param(1) = Trim(param(1))
If param(0) = "" Then _
WScript.Echo "Parse Error: invalid parameter name '" & param(0) & "'."
If param(1) = "" Then param(1) = True
config(section).Add param(0), param(1)
Else
' line is neither parameter nor section name, thus invalid
WScript.Echo "Parse Error: expected parameter definition in line '" _
& line & "'."
End If
End If
Loop
file.Close
Set ParseIni = config
End Function

Import multiline SQL query to single string

In R, how can I import the contents of a multiline text file (containing SQL) to a single string?
The sql.txt file looks like this:
SELECT TOP 100
setpoint,
tph
FROM rates
I need to import that text file into an R string such that it looks like this:
> sqlString
[1] "SELECT TOP 100 setpoint, tph FROM rates"
That's so that I can feed it to the RODBC like this
> library(RODBC)
> myconn<-odbcConnect("RPM")
> results<-sqlQuery(myconn,sqlString)
I've tried the readLines command as follows but it doesn't give the string format that RODBC needs.
> filecon<-file("sql.txt","r")
> sqlString<-readLines(filecon, warn=FALSE)
> sqlString
[1] "SELECT TOP 100 " "\t[Reclaim Setpoint Mean (tph)] as setpoint, "
[3] "\t[Reclaim Rate Mean (tph)] as tphmean " "FROM [Dampier_RC1P].[dbo].[Rates]"
>
The versatile paste() command can do that with argument collapse="":
lines <- readLines("/tmp/sql.txt")
lines
[1] "SELECT TOP 100 " " setpoint, " " tph " "FROM rates"
sqlcmd <- paste(lines, collapse="")
sqlcmd
[1] "SELECT TOP 100 setpoint, tph FROM rates"
Below is an R function that reads in a multiline SQL query (from a text file) and converts it into a single-line string. The function removes formatting and whole-line comments.
To use it, run the code to define the functions, and your single-line string will be the result of running
ONELINEQ("querytextfile.sql","~/path/to/thefile").
How it works: Inline comments detail this; it reads each line of the query and deletes (replaces with nothing) whatever isn't needed to write out a single-line version of the query (as asked for in the question). The result is a list of lines, some of which are blank and get filtered out; the last step is to paste this (unlisted) list together and return the single line.
#
# This set of functions allows us to read in formatted, commented SQL queries
# Comments must be entire-line comments, not on same line as SQL code, and begun with "--"
# The parsing function, to be applied to each line:
LINECLEAN <- function(x) {
x = gsub("\t+", "", x, perl=TRUE); # remove all tabs
x = gsub("^\\s+", "", x, perl=TRUE); # remove leading whitespace
x = gsub("\\s+$", "", x, perl=TRUE); # remove trailing whitespace
x = gsub("[ ]+", " ", x, perl=TRUE); # collapse multiple spaces to a single space
x = gsub("^[--]+.*$", "", x, perl=TRUE); # destroy any comments
return(x)
}
# PRETTYQUERY is the filename of your formatted query in quotes, eg "myquery.sql"
# DIRPATH is the path to that file, eg "~/Documents/queries"
ONELINEQ <- function(PRETTYQUERY,DIRPATH) {
A <- readLines(paste0(DIRPATH,"/",PRETTYQUERY)) # read in the query to a list of lines
B <- lapply(A,LINECLEAN) # process each line
C <- Filter(function(x) x != "",B) # remove blank and/or comment lines
D <- paste(unlist(C),collapse=" ") # paste lines together into one-line string, spaces between.
return(D)
}
# TODO: add eof newline automatically to remove warning
#############################################################################################
Here's the final version of what I'm using. Thanks Dirk.
fileconn<-file("sql.txt","r")
sqlString<-readLines(fileconn)
sqlString<-paste(sqlString,collapse="")
gsub("\t","", sqlString)
library(RODBC)
sqlconn<-odbcConnect("RPM")
results<-sqlQuery(sqlconn,sqlString)
library(qcc)
tph <- qcc(results$tphmean[1:50], type="xbar.one", ylim=c(4000,12000), std.dev=600)
close(fileconn)
close(sqlconn)
This is what I use:
# Set Filename
fileName <- 'Input File.txt'
doSub <- function(src, dest_var_name, src_pattern, dest_pattern) {
assign(
x = dest_var_name
, value = gsub(
pattern = src_pattern
, replacement = dest_pattern
, x = src
)
, envir = .GlobalEnv
)
}
# Read File Contents
original_text <- readChar(fileName, file.info(fileName)$size)
# Convert to UNIX line ending for ease of use
doSub(src = original_text, dest_var_name = 'unix_text', src_pattern = '\r\n', dest_pattern = '\n')
# Remove Block Comments
doSub(src = unix_text, dest_var_name = 'wo_bc_text', src_pattern = '/\\*.*?\\*/', dest_pattern = '')
# Remove Line Comments
doSub(src = wo_bc_text, dest_var_name = 'wo_bc_lc_text', src_pattern = '--.*?\n', dest_pattern = '')
# Remove Line Endings to get Flat Text
doSub(src = wo_bc_lc_text, dest_var_name = 'flat_text', src_pattern = '\n', dest_pattern = ' ')
# Remove Contiguous Spaces
doSub(src = flat_text, dest_var_name = 'clean_flat_text', src_pattern = ' +', dest_pattern = ' ')
try paste(sqlString, collapse=" ")
It's possible to use readChar() instead of readLines(). I had an ongoing issue with mixed commenting (-- or /* */) and this has always worked well for me.
sql <- readChar(path.to.file, file.size(path.to.file))
query <- sqlQuery(con, sql, stringsAsFactors = TRUE)
I use sql <- gsub("\n","",sql) and sql <- gsub("\t","",sql) together.

Resources