]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
curl: add long option '--out-null'
authorStefan Eissing <stefan@eissing.org>
Wed, 2 Jul 2025 07:43:13 +0000 (09:43 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 28 Jul 2025 12:57:38 +0000 (14:57 +0200)
Add a new commandline option --out-null that discards all
response bytes into the void. Replaces non-portable use of
'-o /dev/null' with more efficiency.

Feature added in 8.16.0

Closes #17800

13 files changed:
docs/cmdline-opts/Makefile.inc
docs/cmdline-opts/out-null.md [new file with mode: 0644]
docs/cmdline-opts/output.md
docs/options-in-versions
src/tool_cb_wrt.c
src/tool_getparam.c
src/tool_getparam.h
src/tool_listhelp.c
src/tool_operate.c
src/tool_sdecls.h
tests/data/Makefile.am
tests/data/test756 [new file with mode: 0644]
tests/http/testenv/curl.py

index 6e7f0aafa65280a543dd1e2da8f840e4744db837..c5f99477faeaf0e37d294f3c08cdf8678eeda518 100644 (file)
@@ -181,6 +181,7 @@ DPAGES = \
   ntlm.md \
   oauth2-bearer.md \
   output-dir.md \
+  out-null.md \
   output.md \
   parallel-immediate.md \
   parallel-max.md \
diff --git a/docs/cmdline-opts/out-null.md b/docs/cmdline-opts/out-null.md
new file mode 100644 (file)
index 0000000..cca193e
--- /dev/null
@@ -0,0 +1,26 @@
+---
+c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+SPDX-License-Identifier: curl
+Long: out-null
+Help: Discard response data into the void
+Category: output
+Added: 8.16.0
+Multi: per-URL
+See-also:
+  - output
+  - remote-name
+  - remote-name-all
+  - remote-header-name
+Example:
+  - "https://example.com" --out-null
+---
+
+# `--out-null`
+
+Discard all response output of a transfer silently. This is the more
+efficient and portable version of
+
+    curl https://host.example -o /dev/null
+
+The transfer is done in full, all data is received and checked, but
+the bytes are not written anywhere.
index 48360a49890cfdf0229851b54815b41d61f1510c..e07c0756a6317bdff85ffcfbd85f6fbef96f5e22 100644 (file)
@@ -9,6 +9,7 @@ Category: important output
 Added: 4.0
 Multi: per-URL
 See-also:
+  - out-null
   - remote-name
   - remote-name-all
   - remote-header-name
@@ -56,6 +57,10 @@ Or for Windows:
 
     curl example.com -o nul
 
+Or, even more efficient and portable, use
+
+    curl example.com --out-null
+
 Specify the filename as single minus to force the output to stdout, to
 override curl's internal binary output in terminal prevention:
 
index a9ef8db047143fbb2239d1f1999fdef9d5d56715..bf34e76f2c34b3350c583aa6a099a065763b795c 100644 (file)
 --ntlm                               7.10.6
 --ntlm-wb                            7.22.0
 --oauth2-bearer                      7.33.0
+--out-null                           8.16.0
 --output (-o)                        4.0
 --output-dir                         7.73.0
 --parallel (-Z)                      7.66.0
index da0c8f55f4a09970da643572237438c70d744326..d34c1fa2eeda7db65c488cd07d73fcfc2ef93440 100644 (file)
@@ -141,6 +141,9 @@ size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata)
   intptr_t fhnd;
 #endif
 
+  if(outs->out_null)
+    return bytes;
+
 #ifdef DEBUGBUILD
   {
     char *tty = curl_getenv("CURL_ISATTY");
index f45959068f96b73659204e5bb823aa939d2cb469..ad76c8d280e266589b0713c2c5383d670a2038a2 100644 (file)
@@ -224,6 +224,7 @@ static const struct LongShort aliases[]= {
   {"ntlm",                       ARG_BOOL, ' ', C_NTLM},
   {"ntlm-wb",                    ARG_BOOL|ARG_DEPR, ' ', C_NTLM_WB},
   {"oauth2-bearer",              ARG_STRG|ARG_CLEAR, ' ', C_OAUTH2_BEARER},
+  {"out-null",                   ARG_BOOL, ' ', C_OUT_NULL},
   {"output",                     ARG_FILE, 'o', C_OUTPUT},
   {"output-dir",                 ARG_STRG, ' ', C_OUTPUT_DIR},
   {"parallel",                   ARG_BOOL, 'Z', C_PARALLEL},
@@ -1310,9 +1311,13 @@ static ParameterError parse_output(struct OperationConfig *config,
     return PARAM_NO_MEM;
 
   /* fill in the outfile */
-  err = getstr(&url->outfile, nextarg, DENY_BLANK);
+  if(nextarg)
+    err = getstr(&url->outfile, nextarg, DENY_BLANK);
+  else
+    url->outfile = NULL;
   url->useremote = FALSE; /* switch off */
   url->outset = TRUE;
+  url->out_null = !nextarg;
   return err;
 }
 
@@ -1351,6 +1356,7 @@ static ParameterError parse_remote_name(struct OperationConfig *config,
   url->outfile = NULL; /* leave it */
   url->useremote = toggle;
   url->outset = TRUE;
+  url->out_null = FALSE;
   return PARAM_OK;
 }
 
@@ -1833,6 +1839,8 @@ static ParameterError opt_bool(struct OperationConfig *config,
       return PARAM_LIBCURL_DOESNT_SUPPORT;
     togglebit(toggle, &config->authtype, CURLAUTH_NTLM);
     break;
+  case C_OUT_NULL: /* --out-null */
+    return parse_output(config, NULL);
   case C_BASIC: /* --basic */
     togglebit(toggle, &config->authtype, CURLAUTH_BASIC);
     break;
index 50625d14410fc186865e03f22a9f5d7fa99d52c2..47e87eab9264d5c6e91cdde6ad48e7f6b3042271 100644 (file)
@@ -167,6 +167,7 @@ typedef enum {
   C_NTLM,
   C_NTLM_WB,
   C_OAUTH2_BEARER,
+  C_OUT_NULL,
   C_OUTPUT,
   C_OUTPUT_DIR,
   C_PARALLEL,
index beb034eeaf86b168f8eafa885014736f9a6fd1f1..8572250b5c3ce9622d6a08e3fea912ec9ef8c3a6 100644 (file)
@@ -442,6 +442,9 @@ const struct helptxt helptext[] = {
    "OAuth 2 Bearer Token",
    CURLHELP_AUTH | CURLHELP_IMAP | CURLHELP_POP3 | CURLHELP_SMTP |
    CURLHELP_LDAP},
+  {"    --out-null",
+   "Discard response data into the void",
+   CURLHELP_OUTPUT},
   {"-o, --output <file>",
    "Write to file instead of stdout",
    CURLHELP_IMPORTANT | CURLHELP_OUTPUT},
index 6c4dd87a4a24ebaf187df85f806cfc2b2fae1926..e8dac9691ddb3529bbf55eb0c63f267f314299c6 100644 (file)
@@ -1321,7 +1321,8 @@ static CURLcode single_transfer(struct OperationConfig *config,
         }
       }
 
-      if((urlnode->useremote ||
+      outs->out_null = urlnode->out_null;
+      if(!outs->out_null && (urlnode->useremote ||
           (per->outfile && strcmp("-", per->outfile)))) {
         result = setup_outfile(config, per, outs, skipped);
         if(result)
index fc0c6a546e60cd6a0343aa5ad867e418aa7e1a69..3b01f5592ad89e18e392342f26f08e37fdc691e1 100644 (file)
@@ -74,6 +74,7 @@ struct OutStruct {
   BIT(is_cd_filename);
   BIT(s_isreg);
   BIT(fopened);
+  BIT(out_null);
 };
 
 /*
@@ -95,6 +96,7 @@ struct getout {
   BIT(useremote); /* use remote filename locally */
   BIT(noupload);  /* if set, -T "" has been used */
   BIT(noglob);    /* disable globbing for this URL */
+  BIT(out_null);  /* discard output for this URL */
 };
 /*
  * 'trace' enumeration represents curl's output look'n feel possibilities.
index d8d0ec5db582e4cb1148913af578e03a176f75b1..cc3ea533d50e50db3d9296971c7f7b1c5e00e122 100644 (file)
@@ -108,7 +108,7 @@ test718 test719 test720 test721 test722 test723 test724 test725 test726 \
 test727 test728 test729 test730 test731 test732 test733 test734 test735 \
 test736 test737 test738 test739 test740 test741 test742 test743 test744 \
 test745 test746 test747 test748 test749 test750 test751 test752 test753 \
-test754 test755 \
+test754 test755 test756 \
 test780 test781 test782 test783 test784 test785 test786 test787 test788 \
 test789 test790 test791 test792 test793 \
 \
diff --git a/tests/data/test756 b/tests/data/test756
new file mode 100644 (file)
index 0000000..a205c19
--- /dev/null
@@ -0,0 +1,80 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Response: 1
+Content-Type: text/html
+Content-Length: 8
+
+Hello1!
+</data>
+<data2>
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Response: 2
+Content-Type: text/html
+Content-Length: 8
+
+Hello2!
+</data2>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<features>
+</features>
+
+<name>
+mix --output and --out-null
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/want/%TESTNUMBER http://%HOSTIP:%HTTPPORT/want/%TESTNUMBER0002 --out-null -o -
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /want/%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+GET /want/%TESTNUMBER0002 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+</protocol>
+<stdout>
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Response: 1
+Content-Type: text/html
+Content-Length: 8
+
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Response: 2
+Content-Type: text/html
+Content-Length: 8
+
+Hello2!
+</stdout>
+</verify>
+</testcase>
index 2f19eb37442f37b2434957ee22ce4d71fc0b43d8..26d2dd716cab929ffd16c81b58394b003043ab98 100644 (file)
@@ -587,7 +587,7 @@ class CurlClient:
             extra_args = []
         if no_save:
             extra_args.extend([
-                '-o', '/dev/null',
+                '--out-null',
             ])
         else:
             extra_args.extend([
@@ -636,7 +636,7 @@ class CurlClient:
         if extra_args is None:
             extra_args = []
         extra_args.extend([
-            '-X', 'DELETE', '-o', '/dev/null',
+            '-X', 'DELETE', '--out-null',
         ])
         if with_stats:
             extra_args.extend([
@@ -702,7 +702,7 @@ class CurlClient:
             extra_args = []
         if no_save:
             extra_args.extend([
-                '-o', '/dev/null',
+                '--out-null',
             ])
         else:
             extra_args.extend([