Read one line (and just one line) in Lua. How? - lua

lets suppose that i have this .txt file:
this is line one
hello world
line three
in Lua, i want to creat a string only with the content of line two something like
i want to get a specific line from this file and put into a string
io.open('file.txt', 'r')
-- reads only line two and put this into a string, like:
local line2 = "hello world"

Lua files has the same methods as io library.
That means files have read() with all options as well.
Example:
local f = io.open("file.txt") -- 'r' is unnecessary because it's a default value.
print(f:read()) -- '*l' is unnecessary because it's a default value.
f:close()
If you want some specific line you can call f:read() and do nothing with it until you begin reading required line.
But more proper solution will be f:lines() iterator:
function ReadLine(f, line)
local i = 1 -- line counter
for l in f:lines() do -- lines iterator, "l" returns the line
if i == line then return l end -- we found this line, return it
i = i + 1 -- counting lines
end
return "" -- Doesn't have that line
end

Related

Trying to get some kind of key:value data from a string in Lua

I'm (again) stuck because patterns... so let's see if with a little of help... The case is I have e. g. a string returned by a function that contains the following:
📄 My Script
ScriptID:RL_SimpleTest
Version:0.0.1
ScriptType:MenuScript
AnotherKey:AnotherValue
And, maybe, some more text...
And I'd want to parse it line by line and should the line contains a ":" get the left side content of the line in a variable (k) and the right content in another one (v), so e. g. I'd have k containing "ScriptID" and v containing "RL_SimpleTest" for the second line (the first one should be just ignored) and so on...
Well, I've started with something like this:
function RL_Test:StringToKeyValue(str, sep1, sep2)
sep1 = sep1 or "\n"
sep2 = sep2 or ":"
local t = {}
for line in string.gmatch(str, "([^" .. sep1 .. "]+)") do
print(line)
for k in string.gmatch(line, "([^" .. sep2 .. "]+)") do --Here is where I'm lost trying to get the key/value pair separately and at the same time...
--t[k] = v
print(k)
end
end
return t
end
With the hope once I got isolated the line containing the data in the key:value form that I want to extract, I'd be able to do some kind of for k, v in string.gmatch(line, "([^" .. sep2 .. "]+)") or something so and that way get the two pieces of data, but of course it doesn't work and even though I have a feeling it's a triviality I don't know even where to start, always for the lack of patterns understanding...
Well, I hope at least I exposed it right... Thanks in advance for any help.
local t = {}
for line in (s..'\n'):gmatch("(.-)\r?\n") do
for a, b in line:gmatch("([^:]+):([^:\n\r]+)") do
t[a] = b
end
end
The pattern is quite simple. Match anything that is not a colon that is followed by a colon that is followed by anything that is not a colon or a line break. Put what you want in captures and you're done.
I assume every line is of the format k:v, containing exactly one colon, or containing no colon (no k/v pair).
Then you can simply first match nonempty lines using [^\n]+ (assuming UNIX LF line endings), then match each line using ^([^:]+):([^:]+)$. Breakdown of the second pattern:
^ and $ are anchors. They force the pattern to match the entire line.
([^:]+) matches & captures one or more non-semicolon characters.
This leaves you with:
function RL_Test:StringToKeyValue(str)
local t = {}
for line in str:gmatch"[^\n]+" do
local k, v = line:match"^([^:]+):([^:]+)$"
if k then -- line is k:v pair?
t[k] = v
end
end
return t
end
If you want to support Windows CRLF line endings, use for line in (s..'\n'):gmatch'(.-)\r?\n' do as in Piglet's answer for matching the lines instead.
This answer differs from Piglet's answer in that it uses match instead of gmatch for matching the k/v pairs, allowing exactly one k/v pair with exactly one colon per line, whereas Piglet's code may extract multiple k/v pairs per line.

Split a string on new lines, but include empty lines

Let's say I have a string with the contents
local my_str = [[
line1
line2
line4
]]
I'd like to get the following table:
{"line1","line2","","line4"}
In other words, I'd like the blank line 3 to be included in my result. I've tried the following:
local result = {};
for line in string.gmatch(my_str, "[^\n]+") do
table.insert(result, line);
end
However, this produces a result which will not include the blank line 3.
How can I make sure the blank line is included? Am I just using the wrong regex?
Try this instead:
local result = {};
for line in string.gmatch(my_str .. "\n", "(.-)\n") do
table.insert(result, line);
end
If you don't want the empty fifth element that gives you, then get rid of the blank line at the end of my_str, like this:
local my_str = [[
line1
line2
line4]]
(Note that a newline at the beginning of a long literal is ignored, but a newline at the end is not.)
You can replace the + with *, but that won't work in all Lua versions; LuaJIT will add random empty strings to your result (which isn't even technically wrong).
If your string always includes a newline character at the end of the last line like in your example, you can just do something like "([^\n]*)\n" to prevent random empty strings and the last empty string.
In Lua 5.2+ you can also just use a frontier pattern to check for either a newline or the end of the string: [^\n]*%f[\n\0], but that won't work in LuaJIT either.
If you need to support LuaJIT and don't have the trailing newline in your actual string, then you could just add it manually:
string.gmatch(my_str .. "\n", "([^\n]*)\n")

Lua how to split a word pair string into two separate variables?

I have this code which reads a txt file which contains simply a list of word pairs on each line separate by a space and for every line, the code will split the word pair into separate word and print each word one after the other:
file = io.open( "test.txt", "r" )
temp = {}
for line in file:lines() do
for word in line:gmatch("%w+") do
print(word)
end
end
file:close()
sample test.txt
big small
tall short
up down
left right
output
big
small
tall
short
up
down
left
right
However, I find myself needing to split each word in each word pair into separate variables such that I can use an if statement on word1 in order to do something with word2.
Something like:
file = io.open( "test.txt", "r" )
temp = {}
for line in file:lines() do
for word in line:gmatch("%w+") do
word1 = first word
word2 = second word
if (word1 = "tall") then
print(word2)
end
end
end
file:close()
For a few words you can use captures. This mechanism allows to get specific parts of a match.
See https://www.lua.org/manual/5.4/manual.html#6.4.1
local w1, w2 = line:match("(%w+)%s+(%w+)")
Alternatively, especially for many words you'd put all words into a table.
local line_words = {}
for word in line:gmatch("%w+") do
table.insert(line_words, word)
end
I don't program in Lua myself so apologies beforehand if there's something wrong with the following code
file = io.open( "test.txt", "r" )
temp = {}
for line in file:lines() do
words = {}
for word in line:gmatch("%w+") do table.insert(words, word) end
if (words[1] == "tall") then
print(words[2])
end
end
file:close()
Running this against the test file you gave returns the word short and I assume this is what you needed?

why the function io.write() does not work in lua

I want to count the number of occurrences of words in a text and give the top ten words and their number of occurrences.
I use the function io.open() to open a input file as file-handle, then do something on the file-handle, put the results in a table. then close the input file-handle. and open a output file which is a new file as file-handle try to write the results to this file. but it does not work. the code is following.
the txt "ioinput.txt" is input file which has a article and the txt "iooutput.txt" is the output file
input_file = io.open("ioinput.txt", r)
--[[
This block of code is to count the number of word,
which has been verified by the print function in the following.
--]]
input_file:close()
output_file = io.open("iooutput.txt", a)
local n = 10
for i = 1, n do
output_file:write(words[i], "\t", counter[words[i]], "\n")
--print(words[i], "\t", counter[words[i]], "\n")
end
output_file:flush()
output_file:close()
Please refer to the Lua 5.4 Reference Manual: io.open
io.open (filename [, mode])
This function opens a file, in the mode specified in the string mode.
In case of success, it returns a new file handle.
The mode string can be any of the following:
"r": read mode (the default);
"w": write mode;
"a": append mode;
"r+": update mode, all previous data is preserved;
"w+": update mode, all previous data is erased;
"a+": append update mode, previous data is preserved, writing is only allowed at the end of file.
The mode string can also have a 'b' at the end, which is needed in
some systems to open the file in binary mode.
Please note that the optional mode is to be provided as a string.
In your code
input_file = io.open("ioinput.txt", r) and output_file = io.open("ioinput.txt", a)
your using modes r and a. Both nil values. The mode defaults to "r" which is read mode. You cannot write to a file opened in read mode.

How to remove last line from a string in Lua?

I am using Lua in World of Warcraft.
I have this string:
"This\nis\nmy\nlife."
So when printed, the output is this:
This
is
my
life.
How can I store the entire string except the last line in a new variable?
So I want the output of the new variable to be this:
This
is
my
I want the Lua code to find the last line (regardless of how many lines in the string), remove the last line and store the remaining lines in a new variable.
Thank you.
So I found that Egor Skriptunoff's solutions in the comments worked very well indeed but I am unable to mark his comments as an answer so I'll put his answers here.
This removes the last line and stores the remaining lines in a new variable:
new_str = old_str:gsub("\n[^\n]*$", "")
If there is a new line marker at the end of the last line, Egor posted this as a solution:
new_str = old_str:gsub("\n[^\n]*(\n?)$", "%1")
While this removes the first line and stores the remaining lines in a new variable:
first_line = old_str:match("[^\n]*")
Thanks for your help, Egor.
Most efficient solution is plain string.find.
local s = "This\nis\nmy\nlife." -- string with newlines
local s1 = "Thisismylife." -- string without newlines
local function RemoveLastLine(str)
local pos = 0 -- start position
while true do -- loop for searching newlines
local nl = string.find(str, "\n", pos, true) -- find next newline, true indicates we use plain search, this speeds up on LuaJIT.
if not nl then break end -- We didn't find any newline or no newlines left.
pos = nl + 1 -- Save newline position, + 1 is necessary to avoid infinite loop of scanning the same newline, so we search for newlines __after__ this character
end
if pos == 0 then return str end -- If didn't find any newline, return original string
return string.sub(str, 1, pos - 2) -- Return substring from the beginning of the string up to last newline (- 2 returns new string without the last newline itself
end
print(RemoveLastLine(s))
print(RemoveLastLine(s1))
Keep in mind this works only for strings with \n-style newlines, if you have \n\r or \r\n easier solution would be a pattern.
This solution is efficient for LuaJIT and for long strings.
For small strings string.sub(s1, 1, string.find(s1,"\n[^\n]*$") - 1) is fine (Not on LuaJIT tho).
I scan it backward because it more easier to remove thing from back with backward scanning rather than forward it would be more complex if you scan forward and much simpler scanning backward
I succeed it in one take
function removeLastLine(str) --It will return empty string when there just 1 line
local letters = {}
for let in string.gmatch(str, ".") do --Extract letter by letter to a table
table.insert(letters, let)
end
local i = #letters --We're scanning backward
while i >= 0 do --Scan from bacward
if letters[i] == "\n" then
letters[i] = nil
break
end
letters[i] = nil --Remove letter from letters table
i = i - 1
end
return table.concat(letters)
end
print("This\nis\nmy\nlife.")
print(removeLastLine("This\nis\nmy\nlife."))
How the code work
The letters in str argument will be extracted to a table ("Hello" will become {"H", "e", "l", "l", "o"})
i local is set to the end of the table because we scan it from the back to front
Check if letters[i] is \n if it newline then goto step 7
Remove entry at letters[i]
Minus i with 1
Goto step 3 until i is zero if i is zero then goto step 8
Remove entry at letters[i] because it havent removed when checking for newline
Return table.concat(letters). Won't cause error because table.concat return empty string if the table is empty
#! /usr/bin/env lua
local serif = "Is this the\nreal life?\nIs this\njust fantasy?"
local reversed = serif :reverse() -- flip it
local pos = reversed :find( '\n' ) +1 -- count backwards
local sans_serif = serif :sub( 1, -pos ) -- strip it
print( sans_serif )
you can oneline it if you want, same results.
local str = "Is this the\nreal life?\nIs this\njust fantasy?"
print( str :sub( 1, -str :reverse() :find( '\n' ) -1 ) )
Is this the
real life?
Is this

Resources