Observed behaviour: ``` $ echo word01 word02 word03 floogle word01 word02 word03 floogle $ echo !?word?% echo word03 word03 $ # I expected to get word01 $ echo $BASH_VERSION 5.2.26(1)-release ```
The bash manual for word designators sounds to me as if word01 should be selected. https://www.gnu.org/software/bash/manual/html_node/Word-Designators.html --- Word designators are used to select desired words from the event. A ‘:’ separates the event specification from the word designator. It may be omitted if the word designator begins with a ‘^’, ‘$’, ‘*’, ‘-’, or ‘%’. Words are numbered from the beginning of the line, with the first word being denoted by 0 (zero). Words are inserted into the current line separated by single spaces. ... % The first word matched by the most recent ‘?string?’ search, if the search string begins with a character that is part of a word. --- The page header has words numbered from the beginning of the line, and the description of `%` says first. Thus, I would expect the history expansion to provide word01. Looking at the code that performs % interpolation, that behaviour is hard-coded to do a reverse search on the line. The way this reverse search comes about seems odd, as the search direction choice is used in two different contexts. That is, specifying the reverse direction seems to want to primarily govern whether the line search proceeds by going in the previous direction in all of history or the subsequent history direction. However specifying the reverse direction has the in-line word search start from the end of the line; providing a positive direction value has the word search proceed from the start of the line. Thus, direction controls two distinct facets: history search direction and line search direction. This code behaviour may be intended. If so, the documentation needs updated. But if the documentation describes the intended behaviour, then the code needs adjusted. My opinion would be to allow the user to further guide the shell in whether they want a forward or backward search for that line. I'm happy to help craft a patch for this, but would appreciate some guidance as C isn't a daily driver for me. -- Jess M p.s. These are my notes in reading through the code: :% is processed in lib/readline/histexpand.cL1327 it returns value of search_match, from L296 search_match returns the identified word to % using the value of local_index in a call to history_find_word we're in a block that starts at L273. There, we're substring_okay, so we save "history_search" as the function to search lines with. local_index is set from a function call on L276 we pass in the hardcoded value of -1 for Direction, indicating we want to search through previous history entries, not subsequent history entries lib/readline/history.h describes search direction: L175: /* Search the history for STRING, starting at history_offset. If DIRECTION < 0, then the search is through previous entries, else through subsequent. If the string is found, then current_history () is the history entry, and the value of this function is the offset in the line of that history entry that the string was found in. Otherwise, nothing is changed, and a -1 is returned. */ Jump to lib/readline/histsearch.cL257 -- The history_search() we used wraps history_search_internal() history_search_internal starts on L67 It's longish; our substring search starts processing on L141 with the crucial check of `if (reverse)` the if controls the direction of the scanning of the line. if reverse, start checking the index = line_length i.e. from the end of the string and decrement our index as we search the string. if not reverse, start checking with index = 0 and increment our index as we search across the line. Going back to histexpand.cL276, we hard-coded reverse as the direction in our call to history_search(). Presumably this reverse direction intends only to designate a search through previous history; but it also has the effect of hard-coding us to search the command line for matching word, starting at the end of the string.