модуль для запуска консольных программ и получения их вывода

Здесь выкладываются готовые к использованию макросы и скрипты.
User avatar
zg
zero-gravity
Posts: 5240
Joined: Tue 15 Mar, 2005 17:36
Location: riga
Been thanked: 42 times
Contact:

модуль для запуска консольных программ и получения их вывода

Post by zg » Mon 09 Jun, 2014 16:24

сам модуль:
  1. local ffi=require("ffi")
  2. local C=ffi.C
  3. local function safe_cdef(name,def)
  4. if not pcall(ffi.sizeof,name) then ffi.cdef(def) end
  5. end
  6. safe_cdef("SECURITY_ATTRIBUTES",[[
  7. typedef struct _SECURITY_ATTRIBUTES {
  8.     unsigned long nLength;
  9.     void* lpSecurityDescriptor;
  10.     int32_t bInheritHandle;
  11. } SECURITY_ATTRIBUTES;
  12. ]])
  13. safe_cdef("STARTUPINFOW",[[
  14.   typedef struct _STARTUPINFOW {
  15.     unsigned long cb;
  16.     wchar_t* lpReserved;
  17.     wchar_t* lpDesktop;
  18.     wchar_t* lpTitle;
  19.     unsigned long dwX;
  20.     unsigned long dwY;
  21.     unsigned long dwXSize;
  22.     unsigned long dwYSize;
  23.     unsigned long dwXCountChars;
  24.     unsigned long dwYCountChars;
  25.     unsigned long dwFillAttribute;
  26.     unsigned long dwFlags;
  27.     unsigned short wShowWindow;
  28.     unsigned short cbReserved2;
  29.     unsigned char* lpReserved2;
  30.     void* hStdInput;
  31.     void* hStdOutput;
  32.     void* hStdError;
  33.   } STARTUPINFOW;
  34. ]])
  35. safe_cdef("PROCESS_INFORMATION",[[
  36.   typedef struct _PROCESS_INFORMATION {
  37.     void* hProcess;
  38.     void* hThread;
  39.     unsigned long dwProcessId;
  40.     unsigned long dwThreadId;
  41.   } PROCESS_INFORMATION;
  42. ]])
  43.  
  44. ffi.cdef[[
  45. int32_t CreatePipe(void** hReadPipe,void** hWritePipe,SECURITY_ATTRIBUTES* lpPipeAttributes,unsigned long nSize);
  46. int32_t CreateProcessW(const wchar_t* lpApplicationName,wchar_t* lpCommandLine,SECURITY_ATTRIBUTES* lpProcessAttributes,SECURITY_ATTRIBUTES* lpThreadAttributes,int32_t bInheritHandles,unsigned long dwCreationFlags,void* lpEnvironment,const wchar_t* lpCurrentDirectory,STARTUPINFOW* lpStartupInfo,PROCESS_INFORMATION* lpProcessInformation);
  47. int32_t ReadFile(void* hFile,void* lpBuffer,unsigned long nNumberOfBytesToRead,unsigned long* lpNumberOfBytesRead,void* lpOverlapped);
  48. int32_t CloseHandle(void* hObject);
  49. void* GetStdHandle(unsigned long nStdHandle);
  50. int32_t IsTextUnicode(const void* lpv,int iSize,int* lpiResult);
  51. ]]
  52.  
  53. local advapi32=ffi.load("advapi32")
  54. local CREATE_NO_WINDOW=0x8000000
  55. local STD_INPUT_HANDLE=-10
  56. local STARTF_USESTDHANDLES=0x00000100
  57. local IS_TEXT_UNICODE_ASCII16=0x0001
  58. local IS_TEXT_UNICODE_STATISTICS=0x0002
  59.  
  60. local function ToWChar(str)
  61.   str=win.Utf8ToUtf16(str)
  62.   local result=ffi.new("wchar_t[?]",#str/2+1)
  63.   ffi.copy(result,str)
  64.   return result
  65. end
  66.  
  67. local function split(data,callback)
  68.   while true do
  69.     local s,f=string.find(data,"[\n\r]")
  70.     if s then
  71.       local line=string.sub(data,1,s-1)
  72.       callback(line)
  73.       if string.sub(data,s,f+1)=="\r\n" then f=f+1 end
  74.       data=string.sub(data,f+1)
  75.     else
  76.       break
  77.     end
  78.   end
  79.   return data
  80. end
  81.  
  82. local function Execute(command,folder,callback)
  83.   local pipe_security=ffi.new("SECURITY_ATTRIBUTES")
  84.   pipe_security.nLength=ffi.sizeof(pipe_security)
  85.   pipe_security.bInheritHandle=true
  86.   local pipe_in,pipe_out=ffi.new("void*[1]"),ffi.new("void*[1]")
  87.   if C.CreatePipe(pipe_in,pipe_out,pipe_security,0) then
  88.     local startup=ffi.new("STARTUPINFOW")
  89.     startup.cb=ffi.sizeof(startup)
  90.     startup.dwFlags=STARTF_USESTDHANDLES
  91.     startup.hStdInput=C.GetStdHandle(STD_INPUT_HANDLE)
  92.     startup.hStdOutput=pipe_out[0];
  93.     startup.hStdError=pipe_out[0];
  94.     local info=ffi.new("PROCESS_INFORMATION")
  95.     if C.CreateProcessW(ffi.NULL,ToWChar(command),ffi.NULL,ffi.NULL,true,CREATE_NO_WINDOW,ffi.NULL,ToWChar(folder),startup,info)~=0 then
  96.       C.CloseHandle(pipe_out[0])
  97.       local size=1024
  98.       local buffer=ffi.new("char[?]",size)
  99.       local readed=ffi.new("unsigned long[1]")
  100.       local unicode=ffi.new("int[1]")
  101.       unicode[0]=bit64.bor(IS_TEXT_UNICODE_ASCII16,IS_TEXT_UNICODE_STATISTICS)
  102.       local output=""
  103.       while true do
  104.         C.ReadFile(pipe_in[0],buffer,size,readed,ffi.NULL)
  105.         if readed[0]==0 then
  106.           break
  107.         else
  108.           output=output..(advapi32.IsTextUnicode(buffer,readed[0],unicode)==0 and win.OemToUtf8 or win.Utf16ToUtf8)(ffi.string(buffer,readed[0]))
  109.           output=split(output,callback)
  110.         end
  111.       end
  112.       if #output>0 then callback(output) end
  113.       C.CloseHandle(info.hThread)
  114.       C.CloseHandle(info.hProcess)
  115.     end
  116.     C.CloseHandle(pipe_in[0])
  117.   end
  118. end
  119.  
  120. return Execute
пример, как можно скомпилировать программу на c++ при помощи gcc и если есть ошибки — перейти к месту их обнаружения:
  1. F=far.Flags
  2. Compile=(command,folder,match)->
  3.   editor.SaveFile!
  4.   execute=require"execute"
  5.   rect=far.AdvControl F.ACTL_GETFARRECT
  6.   width,height=rect.Right-rect.Left-5,rect.Bottom-rect.Top-5
  7.   start=false
  8.   items={{"DI_LISTBOX",3,1,width-4,height-2,{}}}
  9.   DlgProc=(dlg,msg,param1,param2)->
  10.     if msg==F.DN_ENTERIDLE and not start
  11.       start=true
  12.       far.SendDlgMessage dlg,F.DM_LISTSETTITLES,1,Title:"Waiting..."
  13.       execute command,folder,(line)->far.SendDlgMessage dlg,F.DM_LISTADD,1,{{Text:line}}
  14.       far.SendDlgMessage dlg,F.DM_LISTSETTITLES,1,Title:"Done!"
  15.   dialog=far.DialogInit win.Uuid"7575E5CC-BDD2-4797-A651-ADA938C0190C",-1,-1,width,height,nil,items,0,DlgProc
  16.   if 1 == far.DialogRun dialog
  17.     item=far.SendDlgMessage dialog,F.DM_LISTGETITEM,1,far.SendDlgMessage(dialog,F.DM_LISTGETCURPOS,1).SelectPos
  18.     if item
  19.       filewitherror,line,pos=match(item.Text)
  20.       if filewitherror
  21.         editor.Editor filewitherror,filewitherror,0,0,-1,-1,bit64.bor(F.EF_NONMODAL,F.EF_IMMEDIATERETURN,F.EF_OPENMODE_USEEXISTING)
  22.         if line
  23.           editor.SetPosition -1,line,pos
  24.   far.DialogFree dialog
  25. Macro
  26.   area:"Editor"
  27.   key:"F9"
  28.   description:"test execute"
  29.   filemask:"*.cpp,*.cc"
  30.   action:->
  31.     path,file,ext=editor.GetInfo!.FileName\match[[(.-)([^\\]-(%.?[^%.\\]*))$]]
  32.     Compile [[g++ -O3 -Wall -Wextra -c -o nul "]]..path..file..[["]],path,(line)->line\match [[(.-[^\\]-):([0-9]+):([0-9]+): ]]
Attachments
execute_v2.7z
(2.32 KiB) Downloaded 199 times

Shmuel
Posts: 5096
Joined: Thu 23 Mar, 2006 21:36
Location: Israel
Has thanked: 11 times
Been thanked: 140 times

Re: модуль для запуска консольных программ и получения их вы

Post by Shmuel » Mon 09 Jun, 2014 16:59

Как всегда - красиво.

Придирки по мелочам:
1. В модуле можно просто сделать return Execute, не нужно создавать 2 таблицы.
2. В макросе можно задать filemask="*.cpp".

User avatar
zg
zero-gravity
Posts: 5240
Joined: Tue 15 Mar, 2005 17:36
Location: riga
Been thanked: 42 times
Contact:

Re: модуль для запуска консольных программ и получения их вы

Post by zg » Mon 09 Jun, 2014 17:23

Shmuel wrote:1. В модуле можно просто сделать return Execute, не нужно создавать 2 таблицы.
что-то я затупил на тему того, что модуль должен возвращать таблицу :?

всё поправил.

AleXH
Posts: 1000
Joined: Sun 12 Apr, 2009 05:21
Been thanked: 1 time

Re: модуль для запуска консольных программ и получения их вы

Post by AleXH » Mon 09 Jun, 2014 18:49

В длинных строках паттерны с дублированными обратными слешами не будут матчиться.

User avatar
zg
zero-gravity
Posts: 5240
Joined: Tue 15 Mar, 2005 17:36
Location: riga
Been thanked: 42 times
Contact:

Re: модуль для запуска консольных программ и получения их вы

Post by zg » Mon 09 Jun, 2014 18:52

AleXH wrote:В длинных строках паттерны с дублированными обратными слешами не будут матчиться.
например?

AleXH
Posts: 1000
Joined: Sun 12 Apr, 2009 05:21
Been thanked: 1 time

Re: модуль для запуска консольных программ и получения их вы

Post by AleXH » Mon 09 Jun, 2014 18:58

проверил - был не прав

User avatar
zg
zero-gravity
Posts: 5240
Joined: Tue 15 Mar, 2005 17:36
Location: riga
Been thanked: 42 times
Contact:

Re: модуль для запуска консольных программ и получения их вы

Post by zg » Mon 09 Jun, 2014 19:06

AleXH wrote:имхо из-за длинных строк слеши должны быть одинарными.
должны, но работать будет и так.

fml2
Posts: 490
Joined: Mon 09 Jul, 2012 22:10
Has thanked: 1 time
Been thanked: 5 times

Re: модуль для запуска консольных программ и получения их вы

Post by fml2 » Mon 09 Jun, 2014 20:32

По-моему, имя переменной "readed" неправильно с точки зрения английской грамматики. Правильно было бы "read". Или это такой программистский сленг? Я знаю, что в программах инога специлаьно пишут "setted", желая подчеркнуть, что это именно прошедшее время от "set". Но с "read" я такого еще не встречал.

Shmuel
Posts: 5096
Joined: Thu 23 Mar, 2006 21:36
Location: Israel
Has thanked: 11 times
Been thanked: 140 times

Re: модуль для запуска консольных программ и получения их вы

Post by Shmuel » Mon 09 Jun, 2014 20:38

Патч, сокращает модуль на 15 строк.
patch.1.7z
(636 Bytes) Downloaded 71 times

User avatar
John Doe
Бюрократ
Posts: 11096
Joined: Wed 27 Apr, 2005 20:42
Has thanked: 12 times
Been thanked: 67 times

Re: модуль для запуска консольных программ и получения их вы

Post by John Doe » Tue 10 Jun, 2014 07:49

Можно сократить ещё, т.к. структура SECURITY_ATTRIBUTES уже определена в winapi.lua

И насколько я понимаю, структуры можно полностью (либо частично) инициализировать прямо в конструкторе:

Code: Select all

  local pipe_security=ffi.new("SECURITY_ATTRIBUTES")
  pipe_security.nLength=ffi.sizeof(pipe_security)
  pipe_security.bInheritHandle=true
Это же можно записать так: local pipe_security=ffi.new("SECURITY_ATTRIBUTES",ffi.sizeof("SECURITY_ATTRIBUTES"),nil,true)

User avatar
John Doe
Бюрократ
Posts: 11096
Joined: Wed 27 Apr, 2005 20:42
Has thanked: 12 times
Been thanked: 67 times

Re: модуль для запуска консольных программ и получения их вы

Post by John Doe » Tue 10 Jun, 2014 08:52

Модуль покритикую.
Код конечно хорошо, но в шапке можно было бы и разжевать интерфейс функции Execute.
Саму функцию (раз уж это модуль) стоило бы сделать более универсальной, сейчас например никак не обрабатываются ошибки запуска.

P.S.
А вообще стоило бы добавить в LuaFAR юникодный аналог io.popen.

Shmuel
Posts: 5096
Joined: Thu 23 Mar, 2006 21:36
Location: Israel
Has thanked: 11 times
Been thanked: 140 times

Re: модуль для запуска консольных программ и получения их вы

Post by Shmuel » Tue 10 Jun, 2014 09:18

John Doe wrote:А вообще стоило бы добавить в LuaFAR юникодный аналог io.popen.
Можно взглянуть на пример "неюникодности" io.popen в LuaFAR?

tstr
Posts: 484
Joined: Thu 16 Apr, 2009 16:15
Has thanked: 2 times
Been thanked: 3 times

Re: модуль для запуска консольных программ и получения их вы

Post by tstr » Tue 10 Jun, 2014 09:21

При запуске через Execute "доходит" ли до процесса CtrlC/CtrlBreak ?

User avatar
zg
zero-gravity
Posts: 5240
Joined: Tue 15 Mar, 2005 17:36
Location: riga
Been thanked: 42 times
Contact:

Re: модуль для запуска консольных программ и получения их вы

Post by zg » Tue 10 Jun, 2014 10:12

tstr wrote:При запуске через Execute "доходит" ли до процесса CtrlC/CtrlBreak ?
да.

User avatar
John Doe
Бюрократ
Posts: 11096
Joined: Wed 27 Apr, 2005 20:42
Has thanked: 12 times
Been thanked: 67 times

Re: модуль для запуска консольных программ и получения их вы

Post by John Doe » Tue 10 Jun, 2014 10:13

Shmuel wrote:Можно взглянуть на пример "неюникодности" io.popen в LuaFAR?
Был не прав, перепутал с функциями os.*
В таком случае в чём преимущество обсуждаемой тут реализации Execute перед стандартной io.popen?

User avatar
zg
zero-gravity
Posts: 5240
Joined: Tue 15 Mar, 2005 17:36
Location: riga
Been thanked: 42 times
Contact:

Re: модуль для запуска консольных программ и получения их вы

Post by zg » Tue 10 Jun, 2014 16:18

John Doe wrote:В таком случае в чём преимущество обсуждаемой тут реализации Execute перед стандартной io.popen?
как у io.popen задать текущую директорию для запускаемого процесса?

Post Reply

Return to “Полезные макросы и скрипты”