CreateProcess, a wywoływanie poleceń systemowych [WIN]

CreateProcess, a wywoływanie poleceń systemowych [WIN]
MI
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:13
0

Witam !
Mam problem z funkcją CreateProcess. Mianowicie założenie jest takie, aby polecenia z pliku *.bat podane jako parametr były wykonywane po kolei na argv[2] procesach. Polecenia zapisane są linijka po linijce, tak aby pobierać linijkę z pliku *.bat, zapisywać ją w tablicy stringów, a później aby jeden proces mógł ją wykonać. Jeżeli plik *.bat ma 90 linijek, a my chcemy aby pracowało 10 procesów równolegle to tak będzie. Niestety nie wiem co robię źle. Błąd jaki dostaję to: 0x2, co oznacza: **The system cannot find the file specified. ** Wszystko pięknie ale nie wiem do czego to się odwołuje. Aktualnie program ma instrukcje jakie ma wykonać w tablicy stringów, ale niestety CreatProcess ich nie wykonuje. Polecenia mają być wykonywane w konsoli [cmd.exe]
Mój kod:

Kopiuj
 
// threads.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <string>
#include <fstream>
#include <process.h>
#include <cstdlib>
#include <sstream>
#include <Windows.h>

using namespace std;

bool fileExists (const string& fileName)
{
        fstream plik;
		plik.open(fileName.c_str(), ios::in);
        if ( plik.is_open() )
        {
			printf("Plik istnieje.\n");
            plik.close();
            return true;
        }
		printf("Plik nie istnieje.\n");
        plik.close();
        return false;
}

int main(int argc, char* argv[])
{
	int iNumOfProcesses;
	string linia;
	int iNumOfLines = 1;
	bool check;
	int iNumOfFors;
	int temp;
	int startAtLine = 0;
	

	int i = 0;
	int j;

	if(argc!= 3)
	{
		cout << "Error: Za malo argumentow!\n";
		getchar();
		return -1;
	}
	while( atoi(argv[2]) == 0 || atoi(argv[2]) > 30)
	{
		cout << "Nie poprawny argument drugi. " <<endl;
		cout << "Argument drugi powinien byc z przedzialu <1 , 30> " << endl;
	}
	iNumOfProcesses = abs(atoi(argv[2]));
		
	check = fileExists(argv[1]);

	fstream plik;
	plik.open(argv[1], ios::in);

	while(!plik.eof())
	{
		getline(plik, linia);
		iNumOfLines = iNumOfLines + 1;
	}
	plik.close();
	
	string *polecenia = new string[iNumOfLines];
	plik.open(argv[1], ios::in);
	while(!plik.eof())
	{
		getline(plik, linia);
		polecenia[i] = linia;
		//cout << linia << endl;
		//cout << polecenia[i] << endl;
		i = i + 1;
	}
	plik.close();


	cout << "Ilosc lini w pliku " << argv[1] << " : " << iNumOfLines << endl;

	PROCESS_INFORMATION *pi = new PROCESS_INFORMATION[iNumOfProcesses];
	HANDLE *hThreadArray = new HANDLE[iNumOfProcesses]; 
	STARTUPINFO si = {0};
	ZeroMemory(&si, sizeof(si));
	int *pi_index = new int[iNumOfProcesses];

	for(i=0; i<iNumOfProcesses; i++)
	{
		ZeroMemory(&pi[i], sizeof(pi[i]));
	}

	si.cb = sizeof(si);

	j = 0;
	iNumOfFors = iNumOfLines / iNumOfProcesses;
	temp = iNumOfLines - ((int)iNumOfFors * iNumOfProcesses);
	startAtLine = iNumOfLines - temp;

	while( j < (int)iNumOfFors)
	{
		for(i = 0; i < iNumOfProcesses; i++)
		{
			pi_index[i] = CreateProcess((LPCWSTR)polecenia[i+(iNumOfProcesses * j)].c_str(), _T(""), 0, 0, 0, 0, 0, 0, &si, &pi[i]);
			//cout << GetLastError() << endl;
			hThreadArray[i] = pi[i].hProcess;
		}
		WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, hThreadArray, TRUE, INFINITE);
		
		for(i = 0; i < iNumOfProcesses; i++)
		{
			CloseHandle(pi[i].hProcess);
			CloseHandle(pi[i].hThread);
		}
		j = j + 1;
	}

	j = 0;
	Sleep(1000);
	if(temp > 1 )
	{
		while(j < temp)
		{
			for(i = 0 ; i < temp; i++)
			{
				pi_index[i] = CreateProcess((LPCWSTR)polecenia[i+startAtLine].c_str(), _T(""), 0, 0, 0, 0, 0, 0, &si, &pi[i]);
				hThreadArray[i] = pi[i].hProcess;
			}
			WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, hThreadArray, TRUE, INFINITE);
		
			for(i = 0; i < temp; i++)
			{
				CloseHandle(pi[i].hProcess);
				CloseHandle(pi[i].hThread);
			}
			j = j + 1;
		}
	}
	
	delete[] pi, pi_index, hThreadArray, polecenia;
	cout << "Konczenie programu\n";

	//system("Pause");
	return 0;
}
edytowany 1x, ostatnio: Michazzz
MI
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:13
0

Znalazłem podobny problem do mojego w internecie, ale nie mogę sobie poradzić z uruchomieniem go:

Kopiuj
CreateProcess(NULL, "cmd /C echo test > c:\test.txt", NULL, NULL, 0, 0, NULL, NULL, &si, &pi))

Dodatkowo u siebie muszę używać rzutowania do LPWSTR, tutaj: http://cboard.cprogramming.com/windows-programming/109024-createprocess-plus-command-line.html działa podobno bez tego. Co robię źle ?

Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:dzień
0

dodaj L przed stringiem

Kopiuj
CreateProcess(NULL, L"cmd /C echo test > c:\\test.txt", ...)

to nie trzeba będfzie rzutowania.

poza tym możesz nie mieć prawa zapisu do c:\.

PS. podwójny backslash.

edytowany 2x, ostatnio: Azarien
MI
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:13
0

Fakt. Praw zapisu na dysku C: nie mam dlatego się wypluwa.

MI
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:13
0

Wpadłem na jeszcze jeden pomysł. Mianowicie tworzę sobie pliki tymczasowe *.bat. Pliki te posiadają po jednej instrukcji. Dalej będzie pętla do tworzenia N procesów, których zadaniem będzie wykonać N plików tymczasowych *.bat. Na początku tworzę folder, do niego wrzucam tymczasowe pliki.
Tak tworzę nazwy, które wrzucam do tablicy stringów:

Kopiuj
	plik.open(argv[1], ios::in);
	for(i = 1; i < iNumOfLines+1; i++)
	{
		getline(plik, linia);
		sprintf(buff, "temp\\temp%d.bat", i);
		fileNameBuffer = buff;
		pliki[i] = fileNameBuffer;
		ofstream MyFile(buff);
		MyFile << linia.c_str();
	}
	plik.close(); 

Tak powstaje proces (póki co chcę wywołać jeden, później na bazie tego będę mógł zrobić pętlę):

Kopiuj
 
	PROCESS_INFORMATION pi2;
	STARTUPINFO si = {0};
	si.cb = sizeof(si);
	ZeroMemory(&si, sizeof(si));
	

A tak wygląda samo wywołanie funkcji tworzącej nowy proces:

Kopiuj
int wynik = CreateProcess((LPCWSTR)pliki[1].c_str(), _T(""), 0, 0, 0, 0, 0, 0, &si, &pi2); 

W jaki sposób przekazać te nazwy plików z tablicy stringów, aby proces wywoływał pliki ?

Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:dzień
0

Na początku tworzę folder, do niego wrzucam tymczasowe pliki.
Do plików tymczasowych istnieje specjalna ścieżka, w C pobierasz ją przez getenv("TEMP"), a w pliku bat %TEMP% (np. %TEMP%\temp1.bat)

MI
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:13
0

Rozumiem. Tylko tutaj nazwałem to plikami tymczasowymi, ale one są plikami tworzonymi na potrzeby procesów, przy kończeniu działania programu, w momencie zwalniania pamięci, folder z tymi plikami jest usuwany. Po prostu potrzebuję sposób na przekazanie ścieżki tych plików do funkcji CreateProcess(). "Na sztywno": L:"temp\temp1.bat" działa, ale chciałbym to wyłuskiwać z tablicy stringów o nazwie pliki, za pomocą pliki[i]. Tak aby mógł to wpuścić później w pętlę.

PS.

Doszedłem do takiego czegoś

Kopiuj
 
	std::wstring str2(pliki[1].length(), L' ');
	std::copy(pliki[1].begin(), pliki[1].end(), str2.begin());

	int wynik = CreateProcess(str2.c_str(), _T(""), 0, 0, 0, 0, 0, 0, &si, &pi2);

, ale GetLastError dostaję o numerze 1821 - "The specified image file did not contain a resource section.". Zgłupiałem.

Ps.2
Pomimo tego, że dostaję błąd o numerze 1812, procesy są wywoływane.

edytowany 2x, ostatnio: Michazzz
Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:dzień
0
Kopiuj
std::wstring str2(pliki[1].length(), L' ');
std::copy(pliki[1].begin(), pliki[1].end(), str2.begin());

nie za dużo kombinujesz?

Kopiuj
std::wstring str2 = pliki[1];

GetLastError dostaję o numerze 1821 - "The specified image file did not contain a resource section.". Zgłupiałem.
GetLastError ma sens tylko gdy funkcja zwróciła błąd:

MSDN napisał(a)

If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.

MI
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:13
0

Napotkałem jeszcze problem, mianowicie na koniec programu sprawdzam czy wszystkie procesy zgłosiły koniec swojej pracy, tak abym mógł bezpiecznie zakończyć działanie całego programu. niestety, program się kończy, a procesy wiszą w systemie i pracują dalej.

Kopiuj
dwSignal = WaitForMultipleObjects(iNumOfProcesses, hThreadArray, TRUE, INFINITE);
 
Azarien
pokaż jak kod teraz po zmianach wygląda.
MI
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:13
0
Kopiuj

	PROCESS_INFORMATION* pi = new PROCESS_INFORMATION[iNumOfProcesses];
	PROCESS_INFORMATION pi2;

	HANDLE *hThreadArray = new HANDLE[iNumOfProcesses]; 

	STARTUPINFO si = {0};
	si.cb = sizeof(si);

	ZeroMemory(&si, sizeof(si));
	int *pi_index = new int[iNumOfProcesses];
	
	for(i=0; i<iNumOfProcesses; i++)
	{
		ZeroMemory(&pi[i], sizeof(pi[i]));
		hThreadArray[i] = 0;
	}

	j = 0;
	i = 0;
	int xd = 0;
	int g = 0;
	DWORD dwSignal;

	while(xd < iNumOfLines+1)
	{
		for(j=0; j<iNumOfProcesses; j++)
		{
			if(hThreadArray[j] == 0)
			{
				std::wstring str2(pliki[xd].length(), L' ');
				std::copy(pliki[xd].begin(), pliki[xd].end(), str2.begin());

				pi_index[j] = CreateProcess(str2.c_str(), _T(""), 0, 0, 0, 0, 0, 0, &si, &pi[j]);
				hThreadArray[j] = pi[j].hProcess;
				xd = xd + 1;
				Sleep(20);
			}
		}
		dwSignal = WaitForMultipleObjects(iNumOfProcesses, hThreadArray, false, INFINITE);

		for(g = 0; g< iNumOfProcesses; g++)
		{
			if(dwSignal == WAIT_OBJECT_0 + g)
			{
				CloseHandle(pi[g].hProcess);
				CloseHandle(pi[g].hThread);
				hThreadArray[g] = 0;
			}
		}
		i = i + 1;
	}


	cout << "\nZabijam wszystkie procesy... ";
	dwSignal = WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, hThreadArray, true, INFINITE);
	cout << " Done!. "<< endl;
	DeleteDirectory(L"temp", true);

	delete[] pi;
	delete[] pi_index;
	delete[] pliki;
	delete[] hThreadArray;

	cout << "\n-------------\n";
	cout << "Koniec programu\n";

	//system("Pause");
	return 0;
}
 
adf88
  • Rejestracja:ponad 21 lat
  • Ostatnio:prawie 12 lat
0

Po stworzeniu procesu nie przerywasz pętli for(j=. Ogólnie jakoś dziko. Zobacz:

Kopiuj
int iNumRunning = 0;

for(int iLine = 0; iLine < iNumOfLines; iLine++)
{
	int iProc = iNumRunning;
	if (iNumRunning >= iNumOfProcesses) {
		iProc = WaitForMultipleObjects(iNumRunning, hThreadArray, false, INFINITE) - WAIT_OBJECT_0;
		assert(iProc >= 0 && iProc < iNumOfProcesses);
		CloseHandle(pi[iProc].hProcess);
		CloseHandle(pi[iProc].hThread);			
	} else {
                iNumRunning++;
        }

	CreateProcessA(pliki[iLine].c_str(), "", 0, 0, 0, 0, 0, 0, &si, &pi[iProc]);
	hThreadArray[iProc] = pi[iProc].hProcess;
	Sleep(20);
}

WaitForMultipleObjects(iNumRunning, hThreadArray, true, INFINITE);
edytowany 3x, ostatnio: adf88
adf88
Jeszcze mała edycja była (...iNumRunning++...)
MI
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:13
0

Mimo wszystko program kończy się wcześniej niż procesy.

adf88
Mimo jakie wszystko? KOD!!!!
MI
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:13
0

Kod jest Twój, dokładnie kopiuj / wklej. Chciałem sprawdzić czy to faktycznie zadziała. Programem uruchamiam pliki batowe, gdzie w liniach zapisane jest jedno polecenie. Polecenia są pobierane i tworzone są pliki "tymczasowe", które zostają wywołane przez X procesów. No i właśnie o to chodzi, że każdy proces wykonuje jakiś tymczasowy plik bat, z jednym poleceniem, testuje to na pakowaniu instalatora LibreOffice, który jest w 9 folderach. Pakuję każdy folder na innym procesie. Wyskakuje mi w konsoli "koniec programu", a wiec została wykonana funkcja usuwająca folder temp. W konsoli w momencie kończenia się procesów pojawia się informacja, że "system nie mógł odnaleźć określonej ścieżki". Chodzi mi o to aby wywołanie:

Kopiuj
 WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, hThreadArray, true, INFINITE); 

czekało na zakończenie się procesów, żeby móc zakończyć program.
Teoretycznie (wydaje mi się tak), że moje rozwiązanie powinno działać.

adf88
  • Rejestracja:ponad 21 lat
  • Ostatnio:prawie 12 lat
0

Mój kod to tylko snippet, nie skompiluje się. Pokaż to, co skompilowałeś.

MI
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:13
0
Kopiuj
PROCESS_INFORMATION* pi = new PROCESS_INFORMATION[iNumOfProcesses];
PROCESS_INFORMATION pi2;

HANDLE *hThreadArray = new HANDLE[iNumOfProcesses]; 

STARTUPINFO si = {0};
si.cb = sizeof(si);

ZeroMemory(&si, sizeof(si));
int *pi_index = new int[iNumOfProcesses];
	
for(i=0; i<iNumOfProcesses; i++)
{
	ZeroMemory(&pi[i], sizeof(pi[i]));
	hThreadArray[i] = 0;
	pi_index[i] = 0;
}

j = 0;
i = 0;
int xd = 0;
int g = 0;
DWORD dwSignal;

int iNumRunning = 0;
 
for(int iLine = 0; iLine < iNumOfLines; iLine++)
{
        int iProc = iNumRunning;
        if (iNumRunning >= iNumOfProcesses) {
                iProc = WaitForMultipleObjects(iNumRunning, hThreadArray, false, INFINITE) - WAIT_OBJECT_0;
                assert(iProc >= 0 && iProc < iNumOfProcesses);
                CloseHandle(pi[iProc].hProcess);
                CloseHandle(pi[iProc].hThread);                        
        } else {
                iNumRunning++;
        }
 
        CreateProcessA(pliki[iLine].c_str(), "", 0, 0, 0, 0, 0, 0, (LPSTARTUPINFOA)&si, &pi[iProc]);
        hThreadArray[iProc] = pi[iProc].hProcess;
        Sleep(20);
}
 
WaitForMultipleObjects(iNumRunning, hThreadArray, true, INFINITE);

cout << " Done!. "<< endl;
DeleteDirectory(L"temp", true);

delete[] pi;
delete[] pi_index;
delete[] pliki;
delete[] hThreadArray;

cout << "\n-------------\n";
cout << "Koniec programu\n";

system("Pause");
return 0;
 

Dokładnie to skompilowałem ;).

adf88
  • Rejestracja:ponad 21 lat
  • Ostatnio:prawie 12 lat
0

A na jakiej podstawie stwierdzasz, że proces wciąż działa? Powinieneś sprawdzić PID.

Spróbuj:

Kopiuj
int iLine = 0;

while (iLine < iNumOfLines || iNumRunning > 0)
{
	int iProc = iNumRunning;
	if (iNumRunning >= iNumOfProcesses || iLine >= iNumOfLines) {
		iProc = WaitForMultipleObjects(iNumRunning, hThreadArray, false, INFINITE) - WAIT_OBJECT_0;
		assert(iProc >= 0 && iProc < iNumOfProcesses);
		CloseHandle(pi[iProc].hProcess);
		CloseHandle(pi[iProc].hThread);
		iNumRunning--;

		cout << "Process closed. PID=" << pi[iProc].dwProcessId << endl;
	}

	if (iLine < iNumOfLines) {
		CreateProcessA(pliki[iLine].c_str(), "", 0, 0, 0, 0, 0, 0, (LPSTARTUPINFOA)&si, &pi[iProc]);
		hThreadArray[iProc] = pi[iProc].hProcess;
		Sleep(20);

		iLine++;
		iNumRunning++;

		cout << "Process created. PID=" << pi[iProc].dwProcessId << endl;
	}
}
cout << " Done!. "<< endl;
DeleteDirectory(L"temp", true);
//...

I zobacz czy faktycznie te procesy wiszą.

edytowany 1x, ostatnio: adf88
MI
Na podstawie menadżera zadań systemu Windows i przydziału procesora na nie.
adf88
Ale jak je identyfikujesz? Patrz na PID, nie na nazwę.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)