I have a nice one-liner that I want to create an alias of. An instance of that one-liner looks like the following:
less +G $(find /var/logs -name 'service-output.root*' -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d' ')
Here it looks into the /var/logs/
directory, does prefix matching for all files starting with service-output.root
and opens the latest with less
.
The alias I want to create should get the directory and the prefix regex as arguments, thus I came up with the following alias function
function l-log {
if [[ ! ${#} -eq 2 ]] || [[ ! -d ${1} ]]; then
echo "Usage: ${0} <dir> <regex>"
return 1
fi
local DIR=${1}
local FILE_REGEX=${2}
find ${DIR} -name '${FILE_REGEX}' -printf '%T@ %p\\n' | sort -n | tail -1 | cut -f2- -d' '
}
The problem is that this does not work.
$ l-log /var/logs/ service-output.root*
zsh: no matches found: service-output.root*
I have enabled function debugging
$ functions -t l-log
and tinkering a bit it seems that the problem is about variable ${FILE_REGEX}
not being properly expanded because it is single quoted (and it has to be single quoted)
$ l-log /apollo/var/logs/ apollo-update.root 1 ↵
+l-log:1> [[ ! 2 -eq 2 ]]
+l-log:1> [[ ! -d /apollo/var/logs/ ]]
+l-log:5> local DIR=/apollo/var/logs/
+l-log:6> local FILE_REGEX=apollo-update.root
+l-log:13> find /apollo/var/logs/ -name '${FILE_REGEX}' -printf '%T@ %p\\n'
+l-log:13> sort -n
+l-log:13> tail -1
+l-log:13> cut -f2- '-d '
I have tried several things like double quoting the variables, escaping single and double quotes, using double double (!) quotes (""${FILE_REGEX}""
) and back ticking (`) but I haven't managed to get +l-log:13>
above to become find /apollo/var/logs/ -name 'service-output.root*' -printf '%T@ %p\\n'
Any help would be great!
There are a few things happening here.
zsh
will try to expand / glob the wildcard before it even gets to the function - that's where the zsh: no matches found
message is coming from. Some ways to call the function:
l-log /var/logs/ 'service-output.root*'
l-log /var/logs/ service-output.root\*
noglob l-log /var/logs/ service-output.root*
find
. Use ${FILE_REGEX}
.The last part can be a bit confusing, since wildcards usually need to be quoted when calling find
. But here the wildcard is contained in a variable, so zsh
is not going to do any globbing unless you specifically ask for that. The single quotes are preventing the variable $FILE_REGEX
from being expanded, and you need that to occur.
BTW, you can do this kind of operation without find
, using just zsh
. Here's one option:
#!/usr/bin/env zsh
llogBase() {
local pattern=${1:?}/**/${2:?}(om[1])
less -- ${~pattern}
}
Some notes on the parts:
pattern=${1:?}/**/${2:?}(om[1])
- this builds a glob pattern that we'll use in the next operation.
.../**/...
- uses the recursive glob operator **/
to traverse the directory tree, similar to find
.${1:?}
- substitutes the directory name supplied by the caller as the first parameter. With the ${ :?}
expansion, this will print an error message and exit the function if the parameter is empty.${2:?}
- substitutes the second parameter, the filename wildcard pattern.(om)
- a glob qualifier. This asks the shell to sort (o)
the results by modification time (m)
, with the most recent file listed first.([1])
- another glob qualifier that selects which items to include. In this case the result will have at most one filename; with the (om)
qualifier it will be the most recent matching file.less -- ${~pattern}
- expands the pattern and calls less
.
${~pattern}
- expands to the contents of the pattern
variable. Since it is a ${~...}
expansion, the shell then performs globbing with the resulting string, and substitutes the single filename that matches the pattern (if one exists).less -- <filename>
- calls less
to display the file. The --
is a guard against filenames that may start with -
.zsh
allows expansions to be nested, so the whole thing could be one slightly cryptic line: less -- ${~:-${1:?}/**/${2:?}(om[1])}
.This function can now be used to search for a file, but the wildcard will need to quoted or escaped as described above, e.g.:
llogBase /var/logs 'service-output.root*'
To avoid quoting patterns for this command, we can use an alias
to always add the noglob
precommand modifier:
alias llog='noglob llogBase'
Calling it with the alias:
llog /var/logs service-output.root*