[{"data":1,"prerenderedAt":1316},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fuse-qgis-process-command-line-runner":3},{"id":4,"title":5,"body":6,"description":1305,"extension":1306,"meta":1307,"navigation":688,"path":1312,"seo":1313,"stem":1314,"__hash__":1315},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fuse-qgis-process-command-line-runner\u002Findex.md","Use the qgis_process Command-Line Runner",{"type":7,"value":8,"toc":1292},"minimark",[9,13,22,31,172,177,217,221,266,284,288,354,388,395,399,402,479,505,598,602,605,642,655,659,662,874,891,894,995,999,1005,1079,1083,1152,1156,1176,1180,1186,1202,1216,1225,1234,1243,1253,1257,1288],[10,11,5],"h1",{"id":12},"use-the-qgis_process-command-line-runner",[14,15,16,17,21],"p",{},"There is a whole class of automation that needs no Python at all: clip this to that, reproject a folder, buffer the roads every morning. ",[18,19,20],"code",{},"qgis_process"," runs any Processing algorithm straight from a shell, with the same algorithm ids and parameter names a PyQGIS script would use, and exits non-zero when it fails — which is all a scheduler needs to know.",[14,23,24,25,30],{},"This recipe belongs to ",[26,27,29],"a",{"href":28},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002F","Headless QGIS and Server Automation",". It covers finding the executable, discovering an algorithm's parameters, running it with real inputs, reading machine-parsable output, and knowing when to switch to a Python script instead.",[14,32,33],{},[34,35,40,44,48,55,72,81,89,95,100,105,109,113,118,122,126,134,139,143,146,150,153,160,163,167],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 256","img","The same buffer operation expressed three ways: a graphical dialog, a Python script and a qgis_process command line, all reaching the same algorithm through the processing registry","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"Three front ends, one algorithm",[45,46,47],"desc",{},"A dialog in the QGIS toolbox, a Python call to processing dot run, and a qgis process command line all converge on the same registered algorithm. The algorithm executes once in compiled code and writes the same output regardless of which front end invoked it.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","256","#f6f3ea",[56,57,58],"defs",{},[59,60,67],"marker",{"id":61,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"qpArrow","0 0 10 10","8","5","7","auto-start-reverse",[68,69],"path",{"d":70,"fill":71},"M0 0 L10 5 L0 10 z","#2f3b35",[73,74,80],"text",{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Same algorithm id, same parameter names, no GUI required",[49,82],{"x":83,"y":84,"width":85,"height":84,"rx":63,"fill":86,"stroke":87,"style":88},"20","52","184","#fffdf7","#59645f","stroke-width:2",[73,90,94],{"x":91,"y":92,"style":93,"fill":78,"textAnchor":79},"112","74","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","toolbox dialog",[73,96,99],{"x":91,"y":97,"style":98,"fill":71,"textAnchor":79},"93","text-anchor:middle;font-size:11px;font-family:sans-serif","a person clicking",[49,101],{"x":83,"y":102,"width":85,"height":84,"rx":63,"fill":103,"stroke":104,"style":88},"118","#edf8e9","#15803d",[73,106,108],{"x":91,"y":107,"style":93,"fill":104,"textAnchor":79},"140","processing.run()",[73,110,112],{"x":91,"y":111,"style":98,"fill":71,"textAnchor":79},"159","inside a Python process",[49,114],{"x":83,"y":85,"width":85,"height":84,"rx":63,"fill":115,"stroke":116,"style":117},"#eff3ff","#2563eb","stroke-width:2.5",[73,119,121],{"x":91,"y":120,"style":93,"fill":116,"textAnchor":79},"206","qgis_process run",[73,123,125],{"x":91,"y":124,"style":98,"fill":71,"textAnchor":79},"225","from any shell or cron",[49,127],{"x":128,"y":129,"width":130,"height":131,"rx":63,"fill":132,"stroke":133,"style":117},"290","98","196","80","#eef7f4","#0f766e",[73,135,138],{"x":136,"y":137,"style":93,"fill":133,"textAnchor":79},"388","128","processing registry",[73,140,142],{"x":136,"y":141,"style":98,"fill":71,"textAnchor":79},"150","native:buffer",[49,144],{"x":145,"y":129,"width":85,"height":131,"rx":63,"fill":86,"stroke":78,"style":88},"560",[73,147,149],{"x":148,"y":137,"style":93,"fill":78,"textAnchor":79},"652","one implementation",[73,151,152],{"x":148,"y":141,"style":98,"fill":71,"textAnchor":79},"identical output",[154,155],"line",{"x1":156,"y1":157,"x2":158,"y2":102,"stroke":71,"style":159},"204","78","284","stroke-width:2;marker-end:url(#qpArrow)",[154,161],{"x1":156,"y1":162,"x2":158,"y2":107,"stroke":71,"style":159},"144",[154,164],{"x1":156,"y1":165,"x2":158,"y2":166,"stroke":71,"style":159},"210","160",[154,168],{"x1":169,"y1":170,"x2":171,"y2":170,"stroke":71,"style":159},"486","138","554",[173,174,176],"h2",{"id":175},"prerequisites","Prerequisites",[178,179,180,207,214],"ul",{},[181,182,183,187,188,190,191,194,195,198,199,202,203,206],"li",{},[184,185,186],"strong",{},"QGIS 3.34 LTR"," or newer installed. ",[18,189,20],{}," ships with the standard packages: ",[18,192,193],{},"\u002Fusr\u002Fbin\u002Fqgis_process"," on Linux, ",[18,196,197],{},"qgis_process-qgis-ltr.bat"," inside the ",[18,200,201],{},"bin"," folder on Windows, and ",[18,204,205],{},"\u002FApplications\u002FQGIS.app\u002FContents\u002FMacOS\u002Fbin\u002Fqgis_process"," on macOS.",[181,208,209,210,213],{},"On a server with no display, ",[18,211,212],{},"QT_QPA_PLATFORM=offscreen"," exported in the environment.",[181,215,216],{},"Input data reachable by absolute path.",[173,218,220],{"id":219},"discover-what-you-can-run","Discover what you can run",[222,223,228],"pre",{"className":224,"code":225,"language":226,"meta":227,"style":227},"language-bash shiki shiki-themes github-dark","qgis_process list | grep -i buffer\nqgis_process help native:buffer\n","bash","",[18,229,230,255],{"__ignoreMap":227},[231,232,234,237,241,245,248,252],"span",{"class":154,"line":233},1,[231,235,20],{"class":236},"svObZ",[231,238,240],{"class":239},"sU2Wk"," list",[231,242,244],{"class":243},"snl16"," |",[231,246,247],{"class":236}," grep",[231,249,251],{"class":250},"sDLfK"," -i",[231,253,254],{"class":239}," buffer\n",[231,256,258,260,263],{"class":154,"line":257},2,[231,259,20],{"class":236},[231,261,262],{"class":239}," help",[231,264,265],{"class":239}," native:buffer\n",[14,267,268,271,272,275,276,279,280,283],{},[184,269,270],{},"Breakdown:"," ",[18,273,274],{},"list"," prints every algorithm the current profile can see, grouped by provider — which is also how you confirm that a provider plugin is actually loaded in this environment. ",[18,277,278],{},"help"," prints the parameters with their types, defaults and accepted values, and it is authoritative for the exact spelling of each parameter name. The same information is available in Python through ",[18,281,282],{},"processing.algorithmHelp(\"native:buffer\")",", so the two interfaces never disagree.",[173,285,287],{"id":286},"run-an-algorithm","Run an algorithm",[222,289,291],{"className":224,"code":290,"language":226,"meta":227,"style":227},"qgis_process run native:buffer -- \\\n  INPUT=\"\u002Fdata\u002Froads.gpkg|layername=roads\" \\\n  DISTANCE=25 \\\n  SEGMENTS=8 \\\n  DISSOLVE=true \\\n  OUTPUT=\"\u002Fdata\u002Foutput\u002Froads_buffer.gpkg\"\n",[18,292,293,309,316,327,337,348],{"__ignoreMap":227},[231,294,295,297,300,303,306],{"class":154,"line":233},[231,296,20],{"class":236},[231,298,299],{"class":239}," run",[231,301,302],{"class":239}," native:buffer",[231,304,305],{"class":250}," --",[231,307,308],{"class":250}," \\\n",[231,310,311,314],{"class":154,"line":257},[231,312,313],{"class":239},"  INPUT=\"\u002Fdata\u002Froads.gpkg|layername=roads\"",[231,315,308],{"class":250},[231,317,319,322,325],{"class":154,"line":318},3,[231,320,321],{"class":239},"  DISTANCE=",[231,323,324],{"class":250},"25",[231,326,308],{"class":250},[231,328,330,333,335],{"class":154,"line":329},4,[231,331,332],{"class":239},"  SEGMENTS=",[231,334,63],{"class":250},[231,336,308],{"class":250},[231,338,340,343,346],{"class":154,"line":339},5,[231,341,342],{"class":239},"  DISSOLVE=",[231,344,345],{"class":250},"true",[231,347,308],{"class":250},[231,349,351],{"class":154,"line":350},6,[231,352,353],{"class":239},"  OUTPUT=\"\u002Fdata\u002Foutput\u002Froads_buffer.gpkg\"\n",[14,355,356,358,359,362,363,366,367,370,371,374,375,378,379,382,383,387],{},[184,357,270],{}," Everything after the bare ",[18,360,361],{},"--"," is a ",[18,364,365],{},"NAME=value"," parameter assignment. Quoting the GeoPackage source protects the pipe from the shell. Enumerated parameters accept either their index or their name — ",[18,368,369],{},"DISSOLVE=true"," for a boolean, ",[18,372,373],{},"METHOD=0"," for a choice. The output path's extension picks the driver, so ending it in ",[18,376,377],{},".gpkg"," writes a GeoPackage and ",[18,380,381],{},".shp"," writes a shapefile with all the limitations that implies, as covered in ",[26,384,386],{"href":385},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fwrite-vector-layer-to-geopackage-pyqgis\u002F","Write a Vector Layer to GeoPackage in PyQGIS",".",[14,389,390,391,394],{},"Progress goes to standard error, results to standard output, and the exit code is zero only when the algorithm reported success — so ",[18,392,393],{},"set -euo pipefail"," at the top of a wrapper script is enough to stop a chain at the first failure.",[173,396,398],{"id":397},"get-machine-readable-output","Get machine-readable output",[14,400,401],{},"Parsing human-readable progress text is a losing game. Ask for JSON instead.",[222,403,405],{"className":224,"code":404,"language":226,"meta":227,"style":227},"qgis_process --json run native:zonalstatisticsfb -- \\\n  INPUT=\"\u002Fdata\u002Fwards.gpkg|layername=wards\" \\\n  INPUT_RASTER=\"\u002Fdata\u002Frainfall.tif\" \\\n  RASTER_BAND=1 \\\n  STATISTICS=2 \\\n  OUTPUT=\"\u002Fdata\u002Foutput\u002Fwards_rain.gpkg\" \\\n  | jq -r '.results.OUTPUT'\n",[18,406,407,423,430,437,447,457,464],{"__ignoreMap":227},[231,408,409,411,414,416,419,421],{"class":154,"line":233},[231,410,20],{"class":236},[231,412,413],{"class":250}," --json",[231,415,299],{"class":239},[231,417,418],{"class":239}," native:zonalstatisticsfb",[231,420,305],{"class":250},[231,422,308],{"class":250},[231,424,425,428],{"class":154,"line":257},[231,426,427],{"class":239},"  INPUT=\"\u002Fdata\u002Fwards.gpkg|layername=wards\"",[231,429,308],{"class":250},[231,431,432,435],{"class":154,"line":318},[231,433,434],{"class":239},"  INPUT_RASTER=\"\u002Fdata\u002Frainfall.tif\"",[231,436,308],{"class":250},[231,438,439,442,445],{"class":154,"line":329},[231,440,441],{"class":239},"  RASTER_BAND=",[231,443,444],{"class":250},"1",[231,446,308],{"class":250},[231,448,449,452,455],{"class":154,"line":339},[231,450,451],{"class":239},"  STATISTICS=",[231,453,454],{"class":250},"2",[231,456,308],{"class":250},[231,458,459,462],{"class":154,"line":350},[231,460,461],{"class":239},"  OUTPUT=\"\u002Fdata\u002Foutput\u002Fwards_rain.gpkg\"",[231,463,308],{"class":250},[231,465,467,470,473,476],{"class":154,"line":466},7,[231,468,469],{"class":243},"  |",[231,471,472],{"class":236}," jq",[231,474,475],{"class":250}," -r",[231,477,478],{"class":239}," '.results.OUTPUT'\n",[14,480,481,271,483,486,487,490,491,493,494,271,498,501,502,504],{},[184,482,270],{},[18,484,485],{},"--json"," switches the whole output to a single JSON document containing the algorithm's inputs, its results and any log messages, which makes it safe to consume from another program. ",[18,488,489],{},".results.OUTPUT"," is the written path — useful when the algorithm chose a name, and essential when chaining, because the next command needs the real output rather than the one you assumed. Note that ",[18,492,485],{}," goes ",[495,496,497],"em",{},"before",[18,499,500],{},"run","; options after the ",[18,503,361],{}," are parameters, not switches.",[14,506,507],{},[34,508,511,514,517,520,523,528,532,536,540,544,547,551,555,558,563,568,571,575,578,584,587,591,594],{"viewBox":509,"role":37,"ariaLabel":510,"xmlns":39},"0 0 760 244","Anatomy of a qgis_process command line showing the executable, global options, the run verb, the algorithm id, the double dash separator and the parameter assignments",[41,512,513],{},"Anatomy of a qgis_process command",[45,515,516],{},"A command line is split into five labelled parts: the executable, global options such as json and project which must come before the verb, the run verb with the algorithm identifier, the double dash separator, and the parameter assignments which are everything after it. A note marks the common mistake of placing a global option after the separator, where it is read as a parameter.",[49,518],{"x":51,"y":51,"width":52,"height":519,"fill":54},"244",[73,521,522],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Options before the verb, parameters after the dashes",[49,524],{"x":525,"y":526,"width":527,"height":84,"rx":63,"fill":86,"stroke":87,"style":88},"16","56","132",[73,529,20],{"x":530,"y":131,"style":531,"fill":78,"textAnchor":79},"82","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif",[73,533,535],{"x":530,"y":129,"style":534,"fill":71,"textAnchor":79},"text-anchor:middle;font-size:10px;font-family:sans-serif","executable",[49,537],{"x":538,"y":526,"width":539,"height":84,"rx":63,"fill":115,"stroke":116,"style":88},"156","152",[73,541,543],{"x":542,"y":131,"style":531,"fill":116,"textAnchor":79},"232","--json --project",[73,545,546],{"x":542,"y":129,"style":534,"fill":71,"textAnchor":79},"global options",[49,548],{"x":549,"y":526,"width":550,"height":84,"rx":63,"fill":132,"stroke":133,"style":88},"316","176",[73,552,554],{"x":553,"y":131,"style":531,"fill":133,"textAnchor":79},"404","run native:buffer",[73,556,557],{"x":553,"y":129,"style":534,"fill":71,"textAnchor":79},"verb + algorithm id",[49,559],{"x":560,"y":526,"width":561,"height":84,"rx":63,"fill":562,"stroke":87,"style":88},"500","60","#efeadd",[73,564,361],{"x":565,"y":566,"style":567,"fill":78,"textAnchor":79},"530","86","text-anchor:middle;font-size:13px;font-weight:bold;font-family:sans-serif",[49,569],{"x":570,"y":526,"width":550,"height":84,"rx":63,"fill":103,"stroke":104,"style":88},"568",[73,572,574],{"x":573,"y":131,"style":531,"fill":104,"textAnchor":79},"656","INPUT=… DISTANCE=…",[73,576,577],{"x":573,"y":129,"style":534,"fill":71,"textAnchor":79},"parameters",[49,579],{"x":525,"y":527,"width":580,"height":581,"rx":63,"fill":582,"stroke":583,"style":88},"728","44","#fdf2e2","#b91c1c",[73,585,586],{"x":75,"y":539,"style":93,"fill":583,"textAnchor":79},"qgis_process run native:buffer -- --json INPUT=…",[73,588,590],{"x":75,"y":589,"style":98,"fill":71,"textAnchor":79},"169","the option lands after the separator and is read as a parameter, then rejected",[73,592,593],{"x":75,"y":156,"style":98,"fill":87,"textAnchor":79},"Other useful globals: --project to supply a project's layers and CRS, --skip-loading-plugins to",[73,595,597],{"x":75,"y":596,"style":98,"fill":87,"textAnchor":79},"222","start faster, and --profile to select the profile whose plugins and connections you need",[173,599,601],{"id":600},"run-a-saved-model","Run a saved model",[14,603,604],{},"Models built in the graphical modeller are algorithms too, so they run exactly the same way.",[222,606,608],{"className":224,"code":607,"language":226,"meta":227,"style":227},"qgis_process run \"\u002Fsrv\u002Fmodels\u002Fflood_pipeline.model3\" -- \\\n  CATCHMENT=\"\u002Fdata\u002Fcatchments.gpkg|layername=catchments\" \\\n  RAINFALL=\"\u002Fdata\u002Frainfall.tif\" \\\n  native:buffer_1:OUTPUT=\"\u002Fdata\u002Foutput\u002Fflood_zones.gpkg\"\n",[18,609,610,623,630,637],{"__ignoreMap":227},[231,611,612,614,616,619,621],{"class":154,"line":233},[231,613,20],{"class":236},[231,615,299],{"class":239},[231,617,618],{"class":239}," \"\u002Fsrv\u002Fmodels\u002Fflood_pipeline.model3\"",[231,620,305],{"class":250},[231,622,308],{"class":250},[231,624,625,628],{"class":154,"line":257},[231,626,627],{"class":239},"  CATCHMENT=\"\u002Fdata\u002Fcatchments.gpkg|layername=catchments\"",[231,629,308],{"class":250},[231,631,632,635],{"class":154,"line":318},[231,633,634],{"class":239},"  RAINFALL=\"\u002Fdata\u002Frainfall.tif\"",[231,636,308],{"class":250},[231,638,639],{"class":154,"line":329},[231,640,641],{"class":239},"  native:buffer_1:OUTPUT=\"\u002Fdata\u002Foutput\u002Fflood_zones.gpkg\"\n",[14,643,644,646,647,650,651,654],{},[184,645,270],{}," Passing a ",[18,648,649],{},".model3"," path where an algorithm id would go runs that model directly, without installing it into a profile. Model inputs use the names given in the modeller. Outputs of intermediate steps are addressed with the ",[18,652,653],{},"algorithmid:PARAMETER"," form, which is how you capture a middle step's result rather than only the final one. This is the cleanest way to hand a repeatable pipeline to a colleague who does not write Python — they run one command, and the pipeline is a file under version control.",[173,656,658],{"id":657},"chain-several-runs-in-a-shell-script","Chain several runs in a shell script",[14,660,661],{},"A fixed sequence of algorithms does not need Python — it needs a small script with the discipline that makes a chain safe to re-run.",[222,663,665],{"className":224,"code":664,"language":226,"meta":227,"style":227},"#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\nexport QT_QPA_PLATFORM=offscreen\nDATA=\u002Fsrv\u002Fgis\u002Fdata\nWORK=$(mktemp -d)\ntrap 'rm -rf \"$WORK\"' EXIT\n\nqgis_process run native:reprojectlayer -- \\\n  INPUT=\"$DATA\u002Froads.gpkg|layername=roads\" \\\n  TARGET_CRS=EPSG:27700 \\\n  OUTPUT=\"$WORK\u002Froads_27700.gpkg\"\n\nqgis_process run native:buffer -- \\\n  INPUT=\"$WORK\u002Froads_27700.gpkg\" \\\n  DISTANCE=25 DISSOLVE=true \\\n  OUTPUT=\"$WORK\u002Froads_buffer.gpkg\"\n\nmv \"$WORK\u002Froads_buffer.gpkg\" \u002Fsrv\u002Fgis\u002Foutput\u002Froads_buffer.gpkg\n",[18,666,667,673,684,690,705,715,734,745,750,764,778,786,798,803,816,828,842,852,857],{"__ignoreMap":227},[231,668,669],{"class":154,"line":233},[231,670,672],{"class":671},"sjoCn","#!\u002Fusr\u002Fbin\u002Fenv bash\n",[231,674,675,678,681],{"class":154,"line":257},[231,676,677],{"class":250},"set",[231,679,680],{"class":250}," -euo",[231,682,683],{"class":239}," pipefail\n",[231,685,686],{"class":154,"line":318},[231,687,689],{"emptyLinePlaceholder":688},true,"\n",[231,691,692,695,699,702],{"class":154,"line":329},[231,693,694],{"class":243},"export",[231,696,698],{"class":697},"s95oV"," QT_QPA_PLATFORM",[231,700,701],{"class":243},"=",[231,703,704],{"class":697},"offscreen\n",[231,706,707,710,712],{"class":154,"line":339},[231,708,709],{"class":697},"DATA",[231,711,701],{"class":243},[231,713,714],{"class":239},"\u002Fsrv\u002Fgis\u002Fdata\n",[231,716,717,720,722,725,728,731],{"class":154,"line":350},[231,718,719],{"class":697},"WORK",[231,721,701],{"class":243},[231,723,724],{"class":697},"$(",[231,726,727],{"class":236},"mktemp",[231,729,730],{"class":250}," -d",[231,732,733],{"class":697},")\n",[231,735,736,739,742],{"class":154,"line":466},[231,737,738],{"class":250},"trap",[231,740,741],{"class":239}," 'rm -rf \"$WORK\"'",[231,743,744],{"class":239}," EXIT\n",[231,746,748],{"class":154,"line":747},8,[231,749,689],{"emptyLinePlaceholder":688},[231,751,753,755,757,760,762],{"class":154,"line":752},9,[231,754,20],{"class":236},[231,756,299],{"class":239},[231,758,759],{"class":239}," native:reprojectlayer",[231,761,305],{"class":250},[231,763,308],{"class":250},[231,765,767,770,773,776],{"class":154,"line":766},10,[231,768,769],{"class":239},"  INPUT=\"",[231,771,772],{"class":697},"$DATA",[231,774,775],{"class":239},"\u002Froads.gpkg|layername=roads\"",[231,777,308],{"class":250},[231,779,781,784],{"class":154,"line":780},11,[231,782,783],{"class":239},"  TARGET_CRS=EPSG:27700",[231,785,308],{"class":250},[231,787,789,792,795],{"class":154,"line":788},12,[231,790,791],{"class":239},"  OUTPUT=\"",[231,793,794],{"class":697},"$WORK",[231,796,797],{"class":239},"\u002Froads_27700.gpkg\"\n",[231,799,801],{"class":154,"line":800},13,[231,802,689],{"emptyLinePlaceholder":688},[231,804,806,808,810,812,814],{"class":154,"line":805},14,[231,807,20],{"class":236},[231,809,299],{"class":239},[231,811,302],{"class":239},[231,813,305],{"class":250},[231,815,308],{"class":250},[231,817,819,821,823,826],{"class":154,"line":818},15,[231,820,769],{"class":239},[231,822,794],{"class":697},[231,824,825],{"class":239},"\u002Froads_27700.gpkg\"",[231,827,308],{"class":250},[231,829,831,833,835,838,840],{"class":154,"line":830},16,[231,832,321],{"class":239},[231,834,324],{"class":250},[231,836,837],{"class":239}," DISSOLVE=",[231,839,345],{"class":250},[231,841,308],{"class":250},[231,843,845,847,849],{"class":154,"line":844},17,[231,846,791],{"class":239},[231,848,794],{"class":697},[231,850,851],{"class":239},"\u002Froads_buffer.gpkg\"\n",[231,853,855],{"class":154,"line":854},18,[231,856,689],{"emptyLinePlaceholder":688},[231,858,860,863,866,868,871],{"class":154,"line":859},19,[231,861,862],{"class":236},"mv",[231,864,865],{"class":239}," \"",[231,867,794],{"class":697},[231,869,870],{"class":239},"\u002Froads_buffer.gpkg\"",[231,872,873],{"class":239}," \u002Fsrv\u002Fgis\u002Foutput\u002Froads_buffer.gpkg\n",[14,875,876,271,878,880,881,884,885,887,888,890],{},[184,877,270],{},[18,879,393],{}," stops the chain at the first failure instead of feeding a missing file to the next step — without it, a failed reprojection produces a confusing error two commands later. The ",[18,882,883],{},"mktemp -d"," plus ",[18,886,738],{}," pair gives every run a private scratch directory that is cleaned up whether the script succeeds, fails or is interrupted. The final ",[18,889,862],{}," is the publish step: the output appears in its destination only once the whole chain has succeeded, and because a rename within one filesystem is atomic, anyone reading that path sees either the previous version or the new one, never a half-written file.",[14,892,893],{},"Two habits make such a script pleasant to operate. Log the algorithm and its parameters before each call so the log can be replayed by hand, and accept the date or region as an argument rather than hard-coding it, so a re-run over yesterday's data needs no editing. At the point where the script grows a conditional or a loop over a variable set of inputs, it has outgrown the shell — move it to Python before it becomes unreadable.",[14,895,896],{},[34,897,900,903,906,909,916,919,922,927,931,937,941,946,950,953,955,959,962,968,971,975,978,984,988,991],{"viewBox":898,"role":37,"ariaLabel":899,"xmlns":39},"0 0 760 236","A chained shell pipeline writing intermediates into a temporary directory and publishing the final result with an atomic rename",[41,901,902],{},"Scratch directory in, atomic publish out",[45,904,905],{},"Two qgis process runs write their intermediate outputs into a temporary directory created for this run. Only the final result is moved into the published location, and the temporary directory is removed whether the script succeeded or failed. Readers of the published path never see a partial file.",[49,907],{"x":51,"y":51,"width":52,"height":908,"fill":54},"236",[56,910,911],{},[59,912,914],{"id":913,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"qpChainArrow",[68,915],{"d":70,"fill":71},[73,917,918],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Nothing lands in the published path until everything worked",[49,920],{"x":525,"y":921,"width":539,"height":526,"rx":63,"fill":86,"stroke":87,"style":88},"72",[73,923,926],{"x":924,"y":925,"style":531,"fill":78,"textAnchor":79},"92","96","source data",[73,928,930],{"x":924,"y":929,"style":534,"fill":71,"textAnchor":79},"116","read-only",[49,932],{"x":130,"y":526,"width":933,"height":934,"rx":935,"fill":582,"stroke":936,"style":117},"356","120","10","#b45309",[73,938,940],{"x":939,"y":131,"style":531,"fill":936,"textAnchor":79},"374","$WORK — removed by the trap",[49,942],{"x":943,"y":925,"width":539,"height":561,"rx":944,"fill":86,"stroke":87,"style":945},"216","6","stroke-width:1.5",[73,947,949],{"x":948,"y":934,"style":534,"fill":71,"textAnchor":79},"292","reprojectlayer",[73,951,952],{"x":948,"y":107,"style":534,"fill":71,"textAnchor":79},"roads_27700.gpkg",[49,954],{"x":75,"y":925,"width":539,"height":561,"rx":944,"fill":86,"stroke":87,"style":945},[73,956,958],{"x":957,"y":934,"style":534,"fill":71,"textAnchor":79},"456","buffer",[73,960,961],{"x":957,"y":107,"style":534,"fill":71,"textAnchor":79},"roads_buffer.gpkg",[154,963],{"x1":964,"y1":965,"x2":966,"y2":965,"stroke":71,"style":967},"368","126","376","stroke-width:2;marker-end:url(#qpChainArrow)",[49,969],{"x":970,"y":921,"width":166,"height":526,"rx":63,"fill":103,"stroke":104,"style":117},"580",[73,972,974],{"x":973,"y":925,"style":531,"fill":104,"textAnchor":79},"660","published path",[73,976,977],{"x":973,"y":929,"style":534,"fill":71,"textAnchor":79},"atomic mv",[154,979],{"x1":980,"y1":981,"x2":982,"y2":983,"stroke":71,"style":967},"168","100","190","110",[154,985],{"x1":986,"y1":983,"x2":987,"y2":981,"stroke":104,"style":967},"552","574",[73,989,990],{"x":75,"y":156,"style":98,"fill":87,"textAnchor":79},"set -euo pipefail stops the chain at the first failure, so step two never runs on a missing file",[73,992,994],{"x":75,"y":993,"style":98,"fill":87,"textAnchor":79},"224","When the sequence grows a conditional, move it to Python",[173,996,998],{"id":997},"qgis-version-compatibility","QGIS version compatibility",[14,1000,1001,1002,1004],{},"The examples target ",[184,1003,186],{}," (Python 3.12).",[1006,1007,1008,1021],"table",{},[1009,1010,1011],"thead",{},[1012,1013,1014,1018],"tr",{},[1015,1016,1017],"th",{},"QGIS version",[1015,1019,1020],{},"Notes",[1022,1023,1024,1038,1048,1060,1068],"tbody",{},[1012,1025,1026,1030],{},[1027,1028,1029],"td",{},"3.16 LTR",[1027,1031,1032,1034,1035,1037],{},[18,1033,20],{}," introduced; no ",[18,1036,485],{},", and models must be installed rather than passed by path.",[1012,1039,1040,1043],{},[1027,1041,1042],{},"3.22 LTR",[1027,1044,1045,1047],{},[18,1046,485],{}," available.",[1012,1049,1050,1053],{},[1027,1051,1052],{},"3.28 LTR",[1027,1054,1055,1056,1059],{},"Adds ",[18,1057,1058],{},"--project"," so algorithms can resolve layers from a project file.",[1012,1061,1062,1065],{},[1027,1063,1064],{},"3.34 LTR",[1027,1066,1067],{},"Baseline for this page.",[1012,1069,1070,1073],{},[1027,1071,1072],{},"3.40 \u002F 3.44",[1027,1074,1055,1075,1078],{},[18,1076,1077],{},"--skip-loading-plugins"," and faster start-up; parameter syntax unchanged.",[173,1080,1082],{"id":1081},"troubleshooting","Troubleshooting",[178,1084,1085,1099,1112,1118,1126,1136,1142],{},[181,1086,1087,1090,1091,1094,1095,1098],{},[184,1088,1089],{},"\"command not found\"."," The executable is not on ",[18,1092,1093],{},"PATH",". On Windows use the OSGeo4W shell or call the ",[18,1096,1097],{},".bat"," by full path; on macOS the binary is inside the app bundle.",[181,1100,1101,1104,1105,1108,1109,387],{},[184,1102,1103],{},"\"algorithm not found\" for a plugin algorithm."," The plugin lives in a profile the run is not using. Pass ",[18,1106,1107],{},"--profile"," with the profile name, and confirm with ",[18,1110,1111],{},"qgis_process list",[181,1113,1114,1117],{},[184,1115,1116],{},"A run succeeds but writes nothing."," The algorithm ran against an input that matched no features — a subset string on the source, or a filter in the parameters. Check the input's feature count before blaming the algorithm.",[181,1119,1120,1123,1124,387],{},[184,1121,1122],{},"The command hangs on a server."," Qt is looking for a display. Export ",[18,1125,212],{},[181,1127,1128,1131,1132,1135],{},[184,1129,1130],{},"A parameter is rejected as invalid."," Check the exact name with ",[18,1133,1134],{},"qgis_process help","; parameter names are case-sensitive and several differ from the label shown in the dialog.",[181,1137,1138,1141],{},[184,1139,1140],{},"The output file is empty but the exit code is zero."," The algorithm succeeded on an input that matched nothing. Check the feature count of the input rather than trusting the exit code alone.",[181,1143,1144,1147,1148,1151],{},[184,1145,1146],{},"Layers referenced from a project do not resolve."," Pass ",[18,1149,1150],{},"--project \u002Fpath\u002Fto\u002Fproject.qgz",", which also supplies the project's CRS and datum-transform settings.",[173,1153,1155],{"id":1154},"conclusion","Conclusion",[14,1157,1158,1160,1161,1163,1164,1166,1167,1169,1170,1172,1173,1175],{},[18,1159,20],{}," turns the whole Processing catalogue into shell commands: ",[18,1162,274],{}," and ",[18,1165,278],{}," to discover, ",[18,1168,500],{}," with ",[18,1171,361],{}," to execute, ",[18,1174,485],{}," to consume the result programmatically, and a model file path where an algorithm id would go. Keep it for single algorithms and fixed chains, and move to a Python script the moment the work needs branching, retries or per-feature logic.",[173,1177,1179],{"id":1178},"frequently-asked-questions","Frequently Asked Questions",[14,1181,1182,1185],{},[184,1183,1184],{},"Does qgis_process need a running QGIS?","\nNo. It starts its own headless QGIS instance, runs the algorithm and exits. Nothing else needs to be open.",[14,1187,1188,1191,1192,1195,1196,1198,1199,387],{},[184,1189,1190],{},"How do I pass multiple input layers?","\nParameters that accept several layers take a ",[18,1193,1194],{},";","-separated list, quoted as one argument. Check ",[18,1197,1134],{}," — the parameter type will say ",[18,1200,1201],{},"multilayer",[14,1203,1204,1207,1208,1211,1212,1215],{},[184,1205,1206],{},"Can I set the output CRS?","\nOnly where the algorithm exposes one, such as ",[18,1209,1210],{},"native:reprojectlayer","'s ",[18,1213,1214],{},"TARGET_CRS",". Otherwise the output inherits the input's CRS, as it does in the GUI.",[14,1217,1218,1221,1222,1224],{},[184,1219,1220],{},"Is it faster than Python?","\nFor one algorithm, effectively identical — the same compiled code runs. For a thousand calls, a single Python process wins easily, because ",[18,1223,20],{}," pays the start-up cost every time.",[14,1226,1227,1230,1231,1233],{},[184,1228,1229],{},"Can I see what a run actually did afterwards?","\nYes — ",[18,1232,485],{}," output includes the resolved input parameters alongside the results, so redirecting it to a file next to the output gives a record of exactly which parameters produced which artefact. That pairing is worth keeping for anything published.",[14,1235,1236,1239,1240,1242],{},[184,1237,1238],{},"Does it respect the project's datum transformations?","\nOnly when you pass ",[18,1241,1058],{},". Without it the run uses defaults, which for most CRS pairs is fine and for a national grid with its own transformation grid is not — the difference is metres, and silent.",[14,1244,1245,1248,1249,387],{},[184,1246,1247],{},"How do I use it inside cron?","\nWrap it in a small shell script that sets the environment explicitly and redirects both streams to a log file. The details are in ",[26,1250,1252],{"href":1251},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fschedule-pyqgis-scripts-with-cron\u002F","Schedule PyQGIS Scripts with cron",[173,1254,1256],{"id":1255},"related","Related",[178,1258,1259,1264,1270,1276,1282],{},[181,1260,1261,1263],{},[26,1262,29],{"href":28}," — the guide this recipe belongs to",[181,1265,1266],{},[26,1267,1269],{"href":1268},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Frun-pyqgis-in-docker-container\u002F","Run PyQGIS in a Docker Container",[181,1271,1272],{},[26,1273,1275],{"href":1274},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-processing-algorithm-from-script\u002F","Run a Processing Algorithm from a Script",[181,1277,1278],{},[26,1279,1281],{"href":1280},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002F","Chaining Processing Algorithms in PyQGIS",[181,1283,1284],{},[26,1285,1287],{"href":1286},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis\u002F","Write a Custom Processing Algorithm in PyQGIS",[1289,1290,1291],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}",{"title":227,"searchDepth":257,"depth":257,"links":1293},[1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304],{"id":175,"depth":257,"text":176},{"id":219,"depth":257,"text":220},{"id":286,"depth":257,"text":287},{"id":397,"depth":257,"text":398},{"id":600,"depth":257,"text":601},{"id":657,"depth":257,"text":658},{"id":997,"depth":257,"text":998},{"id":1081,"depth":257,"text":1082},{"id":1154,"depth":257,"text":1155},{"id":1178,"depth":257,"text":1179},{"id":1255,"depth":257,"text":1256},"Run any QGIS Processing algorithm from a shell with qgis_process — discover parameters, pass layers and outputs, read JSON results, and run saved models without writing Python.","md",{"slug":1308,"type":1309,"breadcrumb":1310,"datePublished":1311,"dateModified":1311},"use-qgis-process-command-line-runner","article","qgis_process Runner","2026-08-10","\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fuse-qgis-process-command-line-runner",{"title":5,"description":1305},"pyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fuse-qgis-process-command-line-runner\u002Findex","jMiEBxTLHI8opTgVcZho8wxZGyMYpBSSvosLeNYJ2do",1786401337551]