简体   繁体   中英

An alias in .bashrc fails vs command line succeeds

I run this from a user's home dir to show me the most recent files while omitting the shell profile files:

find ./ -type f -printf "%T@ %p\n"|grep -vP "/\.(bash|emacs|gtkrc|kde/|zshrc)" |sort -n| tail -10|cut -f2- -d" "|while read EACH; do ls -l "$EACH"; done;

This works, but just not as well when placed in my .bashrc as an alias:

alias recentfiles='find ./ -type f -printf "%T@ %p\n"|grep -vP "/\.\(bash|emacs|gtkrc|kde/|zshrc\)"|sort -n| tail -10|cut -f2- -d" "|while read EACH; do ls -l "$EACH"; done;'

In the image you see the results without doing any filtering, followed by the desired result using grep -v for filtering which works on command line. Then final result - only partially succeeds in weeding out those files.

I have tried using bash_ and [b]ash. Not even bas (which fails to even get .basin) work ?!? And also I can use macs or acs AND still get the .emacs omitted so obviously the syntax in my alias is not respecting the /. either. Not a problem with reserved words as I originally thought.

终端截图

I DO get the expected results if I place my original command as is in a file and then use the alias that way: alias recentfiles='. /root/mycommands/recentfiles'

Can someone explain or point me to a reference to understand what is at play here? I wouldn't know what phrase with the proper terms to search on.

This should fix your problems:

alias recentfiles='find ./ -type f -printf "%T@ %p\n"|grep -vP "/\.(bash|emacs|gtkrc|kde/|zshrc)"|sort -n| tail -10|cut -f2- -d" "|while read EACH; do ls -l "$EACH"; done;'

The issue is with grep -P , where -P makes it use the perl regular expressions. In perl there is no need to use \\ in grouping. So (bash|emacs|...) instead of \\(bash|emacs|...\\) . I really doubt it worked outside of .bashrc, unless you have some alias for grep which make it behave differently outside of .bashrc .

As other have said in the comments, your filtering is inefficient. Better rewrite your command with:

find ./ \( -name ".bash*" -o -name ".emacs*" -o -name .gtkrc -o -name .kde -o -name .zshrc \) -prune -o \( -type f -printf "%T@ %p\n" \) |sort -n| tail -10|cut -f2- -d" "| tr "\n" "\0" | xargs -0 ls -l;

This way it will not waste time searching files inside .emacs.d/ or inside .kde/ , and will immediately prune the search. Also, xargs -0 ls -l is so much shorter and clearer than the while loop.

To avoid issues with filenames that contain newlines, better use \\0 characters, that are never part of a file name:

find ./ \( -name ".bash*" -o -name .emacs -o -name .gtkrc -o -name .kde -o -name .zshrc \) -prune -o \( -type f -printf "%T@ %p\0" \) |sort -n -z | tail -z -n -10| cut -z -f2- -d" " | xargs -0 ls -l

Part 1: Fixing The Issue

Use a function instead.

There are several major issues with aliases:

  • Because you pass your content to be string-prefixed inside quotes when creating an alias, it's parsed differently than it would be when typed directly at the command line.

  • Because an alias is simple prefix substitution, they don't have their own arguments ( $1 , $2 , etc); they don't have a call stack; debugging mechanisms like PS4=':$BASH_SOURCE:$LINENO+'; set -x PS4=':$BASH_SOURCE:$LINENO+'; set -x can't tell you which file code from an alias originated in; etc.

  • Aliases are an interactive feature; POSIX doesn't mandate that shells support them at all, and they're turned off by default during script execution.

Functions solve all these problems.

recentfiles() {
  find ./ \
      '(' -name '.bash*' -o -name '.emacs*' -o -name .gtkrc -o -name .kde -o -name .zshrc ')' -prune \
      -o -type f -printf "%T@ %p\0" |
    sort -nz |
    tail -z -n -10 |
    while read -d' ' _ && IFS= read -r -d '' file; do
      printf '%s\0' "$file"
    done |
    xargs -0 ls -ld --
}

Note that I also made several other changes:

  • Instead of using \\n as a separator, the above code uses \\0 . This is because newlines can be found in filenames; a file that contained newlines in its name could look like any number of files, with any arbitrary sizes it wanted, to the rest of your pipeline. (Unfortunately, POSIX doesn't require that sort and tail support newline delimiters, so the -z options used above are GNUisms).
  • Instead of using grep -v to remove dotfiles, I used the -prune option to find . This is particularly important for directories like .kde , since it stops find from spending the time and I/O bandwidth to recurse down directories for which you intend to throw the results away anyhow.
  • For documentation of the importance of the IFS= and -r arguments used in the while read loop, see BashFAQ #1 . Both of these improve behavior in presence of unusual filenames (clearing IFS prevents trailing whitespace from being stripped; passing -r prevents literal backslashes from being elided).
  • Instead of grep -P -- a GNU extension which is only available if grep was compiled with libpcre support -- my first cut (prior to moving to find -prune ) switched to grep -E , which is adequately expressive, much more widely available, and lends itself to higher performance implementations .

Part 2: Explaining The Issue

Running your alias after set -x , we see:

+ find ./ -type f -printf '%T@ %p\n'
+ grep -vP '/\.\(bash|emacs|gtkrc|kde/|zshrc\)'
+ sort -n
+ tail -10
+ cut -f2- '-d '
+ read EACH

By contrast, running the command it was intended to wrap, we see:

+ find ./ -type f -printf '%T@ %p\n'
+ grep -vP '/\.(bash|emacs|gtkrc|kde/|zshrc)'
+ sort -n
+ tail -10
+ cut -f2- '-d '
+ read EACH

In the command itself, there are no literal backslashes before ( and ) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM