You can think of _ENV as a table that implicitly holds your global variables. For example, try running the lua shell and executing the following in it (here the > is the shell prompt):
Lua:
> x = "this is x"
> print(_ENV.x)
> _ENV.x = "this is _ENV.x"
> print(x)
You'll see that the value of _ENV.x is the value you assigned to x, and viceversa. This is because x and _ENV.x are the same thing.
That is, x is not really a 'global' variable but it's actually a field of the table _ENV. You don't have to refer to it as _ENV.x though (but you can if you want to) because Lua automatically understands that you are referring to _ENV.x if there is no local variable with the name x in scope.
The table _ENV is called
the environment, which (my guess) is short for
the environment where the code is executed in. This gives you a hint on what it can be used for: to create separate environments where to execute different scripts or chunks of code, so that (for example) they can use the same names for 'global' variables without stepping on each other's feet. Or to sandbox them, by providing them an environment with limited functionalities (for example you may not want them to use the io library, so you would provide an _ENV without it).
The table _G is the
global environment, which you can think of as the default _ENV. Another experiment you can do in the Lua shell is this:
Lua:
> print(_G)
> print(_ENV)
You'll see that they are the same table.
Yet another experiment that may be helpful in getting to know _ENV (and/or _G) is to print its contents:
Lua:
> for k, v in pairs(_ENV) do print(k, v) end
You'll see that its fields are the exactly the variables that you are likely accustomed to think of as global variables (including the standard libraries, such as 'math', or the core functions such as 'require' or 'print').
When you're comfortable with all the above, the next step is to read the manual entries for
load( ) and
loadfile( ), which are the functions you use to execute other Lua code from Lua code. They both accept an optional env parameter, which allows you to customize the environment where to execute the loaded code, giving you the opportunity to do things such as those mentioned earlier (sandboxing and separate environments).