On 2023-12-25 16:58, Pádraig Brady wrote: > On 25/12/2023 21:25, Kaz Kylheku wrote: >> On 2023-12-22 10:09, Evan Tremblay wrote: >>> so, if you run: >>> cat * >>> >>> it wont run(unless you use --show-all). >> >> Are scrambling to win a stupidest post of 2023 contest or something? > > That is not an appropriate response.
Sorry, Christmas is a little touch-and-go so details fall by the wayside. I now have a moment to explain why it deserves being called stupid. It is not because of the evaluation strategy of * being expanded by the shell. The feature is actually implementable. The cat program has a way of determining that it has been passed all the names that may arise from the expansion of *. (Modulo a minor sampling-related race condition.) Namely, it can just call glob("*", ...) and compare the results to its argument vector. It's a complete nonstarter because: 1. Unknown numbers of scripts out there depend on "cat *" just working. If an option is suddenly required to make that work, those scripts break. A legitimate use might look like, oh: # get the contents of all files in target_dir into file (cd $target_dir; cat * > $target_contents) 2. In relation to 1, such an option is incompatible with other implementations of cat (including prior versions of the same Coreutils one) and, importantly, with the POSIX standard. POSIX prohibits an implementation of cat from requiring an opt-in option in order for "cat *" to do what it is told. It's possible to have it as an opt-in behavior, and that would also eliminate the inefficiency (from regular use): cat --not-all-files could do the expansion of *, and fail if all the files in that expansion are present in the command line. It's technically objectionable to include such hacks in the core utilities. Presumably, this protects the interactive user from accidentally catting all the files in a large directory. A protective feature in the interactive environment can be obtained by writing a shell function in Bash. Shell functions in people's personal, private environments *can* be ugly hacks. All that matters is whether they are acceptable to that individual. Here is a basic crack at it: cat() { local -a orig_args=("$@") local -a star_files=(*) local all_present=y local occurs_not local i local j # Crudely skip arguments that look like options while true; do case "$1" in -* | --* ) shift ;; * ) break ;; esac done # get remaining args into args array local -a args=("$@") # determine whether all files in star_files are in orig_args for i in "${star_files[@]}"; do occurs_not=y for j in "${args[@]}"; do if [ "$i" = "$j" ]; then occurs_not= fi done if [ $occurs_not ]; then all_present= break fi done if [ $all_present ] ; then echo "cat: all files that match * present in command line!" return 1 fi command cat "${orig_args[@]}" } This has O(M * N) behavior, in the number of file arguments M and number of files that match the * pattern, N. This is kind of a "feature". When you do "cat file.txt", you hardly notice a slowdown; it doesn't take much time to compare that one file to thousands of files. But when you type "cat *" in a large directory, it will just sit there for a while, and that alone alerts the user that the very thing they are trying to avoid has happened. Rather than waiting for the inevitable diagnostic, they can just hit Ctrl-C. There are ways to speed it up by taking advantage of the contents of the * expansions being sorted. We change the requirements to this: we look for situations when the command line contains, as a contiguous subsequence, the sequence produced by *. If Bash had macros, we could do things like this in better ways. Imagine we could do this: macro cat() { } where "cat * *.txt $X" receives three arguments that are literally '*', '*.txt' and '$X', with no expansion having taken place, only a division into fields. It could then check that '*' does not occur, and then use eval to execute command cat "$@".