惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Darknet – Hacking Tools, Hacker News & Cyber Security
NISL@THU
NISL@THU
S
Securelist
O
OpenAI News
S
Security Affairs
Cyberwarzone
Cyberwarzone
T
Threatpost
Simon Willison's Weblog
Simon Willison's Weblog
The Last Watchdog
The Last Watchdog
L
LINUX DO - 最新话题
C
Cisco Blogs
PCI Perspectives
PCI Perspectives
SecWiki News
SecWiki News
S
Secure Thoughts
GbyAI
GbyAI
I
Intezer
AWS News Blog
AWS News Blog
F
Fortinet All Blogs
I
InfoQ
阮一峰的网络日志
阮一峰的网络日志
Google Online Security Blog
Google Online Security Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
A
About on SuperTechFans
S
Schneier on Security
P
Proofpoint News Feed
雷峰网
雷峰网
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
小众软件
小众软件
H
Heimdal Security Blog
Microsoft Security Blog
Microsoft Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs
V
V2EX
L
Lohrmann on Cybersecurity
Security Latest
Security Latest
A
Arctic Wolf
Apple Machine Learning Research
Apple Machine Learning Research
H
Hacker News: Front Page
Cisco Talos Blog
Cisco Talos Blog
Webroot Blog
Webroot Blog
T
Tenable Blog
MyScale Blog
MyScale Blog
博客园 - 司徒正美
S
SegmentFault 最新的问题
Y
Y Combinator Blog
腾讯CDC
Hacker News: Ask HN
Hacker News: Ask HN
M
MIT News - Artificial intelligence
G
GRAHAM CLULEY

alexwlchan’s notes

A single command to test all my changed Go packages Disable the new message animations in WhatsApp Finding high-churn folders that bother Backblaze Always-on SSH agent forwarding with my Git pushes Managing the caption of a photo with AppleScript (but not PhotoKit) Goodhart’s and Campbell’s Law are different Notes from The Cornishman No. 176 (Spring 2026) Notes from The Cornishman No. 176 (Spring 2026) GitUp can’t diff text files larger than 8MB Home Testing the width of a page on a mobile device using Playwright Disable AirPods charging notifications Start a Caddy server in a subprocess during a Python session Filter a list of JSON object based on a list of tags HOME_GET_ME_HOME is a Citymapper Shortcuts action The FileExistsError exception exposes a filename attribute The red-lined bubble snail Why can’t Python connect to example.com? Useful type hints for Python How to truncate the middle of long command output AirPlay Receiver can interfere with Flask apps What’s the main prefix in SQLite queries? The file(1) command can read SQLite databases My randline project is tested by Crater Drawing an image with Liquid Glass using SwiftUI Previews Road signs in the Soviet union don’t have circular heads Setting up golink in my personal tailnet Create a file atomically in Go Get a map of IP addresses for devices in my tailnet Use SQL triggers to prevent overwriting a value Testing date formatting with date-fns-tz and different timezones The “strangler” pattern is named after a tree, not an act of violence Place with the same name, but different etymology
The SQLite command line shell will count your unclosed parentheses
2026-02-20 · via alexwlchan’s notes

If the prompt starts with (x1 or (x2, it means you’ve opened some parentheses and not closed them yet.

While writing my previous note, I noticed an unexpected prefix in the SQLite shell:

sqlite> CREATE TABLE KeyValuePairs (
(x1...>     Key   TEXT NOT NULL PRIMARY KEY,
(x1...>     Value TEXT NOT NULL
(x1...> );

What does that (x1 prefix mean? I couldn’t find any reference to it online, so I had to read the SQLite source code.

The relevant code is in shell.c.in. First I found the default prompts, and two variables where the main and continuation prompts are stored:

406/*

407** Prompt strings. Initialized in main. Settable with

408** .prompt main continue

409*/

410#define PROMPT_LEN_MAX 128

411/* First line prompt. default: "sqlite> " */

412static char mainPrompt[PROMPT_LEN_MAX];

413/* Continuation prompt. default: " ...> " */

414static char continuePrompt[PROMPT_LEN_MAX];

Lines 406–414 of src/shell.c.in in the SQLite source repository. All SQLite code is in the public domain.

Looking at where those variables get used leads to another interesting snippet, which names this feature as “dynamic continuation prompt”:

444/*

445** Optionally disable dynamic continuation prompt.

446** Unless disabled, the continuation prompt shows open SQL lexemes if any,

447** or open parentheses level if non-zero, or continuation prompt as set.

448** This facility interacts with the scanner and process_input() where the

449** below 5 macros are used.

450*/

451#ifdef SQLITE_OMIT_DYNAPROMPT

452# define CONTINUATION_PROMPT continuePrompt

460#else

461# define CONTINUATION_PROMPT dynamicContinuePrompt()

Lines 444–461 of src/shell.c.in in the SQLite source repository.

And looking for the definition of that dynamicContinuePrompt function, I can see it updating a dynPrompt.dynamicPrompt variable with expressions like the (x1 I saw in my SQLite shell:

499/* Upon demand, derive the continuation prompt to display. */

500static char *dynamicContinuePrompt(void){

501 if( continuePrompt[0]==0

502 || (dynPrompt.zScannerAwaits==0 && dynPrompt.inParenLevel == 0) ){

503 return continuePrompt;

504 }else{

505 if( dynPrompt.zScannerAwaits ){

506 size_t ncp = strlen(continuePrompt);

507 size_t ndp = strlen(dynPrompt.zScannerAwaits);

508 if( ndp > ncp-3 ) return continuePrompt;

509 shell_strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits);

510 while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' ';

511 shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,

512 PROMPT_LEN_MAX-4);

513 }else{

514 if( dynPrompt.inParenLevel>9 ){

515 shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4);

516 }else if( dynPrompt.inParenLevel<0 ){

517 shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4);

518 }else{

519 shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4);

520 dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel);

521 }

522 shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,

523 PROMPT_LEN_MAX-4);

524 }

525 }

526 return dynPrompt.dynamicPrompt;

527}

528#endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */

Lines 499–528 of src/shell.c.in in the SQLite source repository.

I don’t completely understand this function, but I think I get the general gist. The first branch is looking for “open SQL lexemes”, or unterminated strings, while the second branch is counting open parentheses. I can compare this to what I see in the SQLite shell:

  • If you have between 1 to 9 unclosed parentheses, the prompt starts with (x and the number of unclosed parens:
    sqlite> (
    (x1...>
    sqlite> (((((((((
    (x9...>
  • If you have 10 or more unclosed parentheses, the prompt starts with prints (..:
    sqlite> ((((((((((
    (x.....>
  • If you have more closed parentheses than you've opened, the prompt starts with prints )x!:

    sqlite> )))
    )x!...>
  • If you’re in this state, I’m not sure if it's ever possible to get back to a valid SQL expression?

  • If you have an unfinished string, square bracket, or multi-line comment, the prompt starts with the quote character you need to close the string:

    sqlite> SELECT '
    '  ...> hello world
    '  ...> '
    
    hello world
    
    sqlite> SELECT "
    "  ...> hello world
    "  ...> "
    
    hello world
    
    sqlite> [
    [  ...> 
    
    sqlite> /*
    /* ...> 
    

I wonder where this behaviour came from? It feels like the sort of thing that might have come from Lisp, which is famous for having lots of brackets and exactly where this sort of indicator might be useful, whereas I imagine writing a heavily nested expression in the SQLite shell interface is comparatively rare.