shell alias executes in home directory - bash

I have following line in my .zshrc file:
alias clean="sed -i 's/\r//g; s/ /\t/g' $(find . -maxdepth 1 -type f)"
but when I try to execute it in /path/to/some/directory the output is:
sed ./.Xauthority
./.lesshst: No such file or directory
.Xauthority and .lesshst are both in my home directory.
Substitutiong . with $(pwd) dos not help.

When defining the alias you've used double quotes to encompass the entire (alias) definition. This has the effect of actually running the find command at the time the alias is defined.
So when the alias is created it will pick up a list of files from the directory in which the alias is being defined (eg, in your home directory when sourcing .zshrc).
You can see this happening in the following example:
$ cd /tmp
$ pwd
/tmp
$ ls -l
total 36036
drwxrwxrwt+ 1 myid None 0 Oct 10 11:31 ./
drwxr-xr-x+ 1 myid None 0 Jul 12 17:28 ../
-rw-r--r-- 1 myid Administrators 0 Oct 10 11:31 a
-rw-r--r-- 1 myid Administrators 0 Oct 10 11:31 b
-rw-r--r-- 1 myid Administrators 0 Oct 10 11:31 c
-rw-r--r-- 1 myid Administrators 0 Oct 10 11:31 d
-rw-r--r-- 1 myid Administrators 36864002 Jun 6 17:29 giga.txt
drwx------+ 1 myid Administrators 0 Mar 8 2020 runtime-xward/
$ alias clean="sed -i 's/\r//g; s/ /\t/g' $(find . -maxdepth 1 -type f)"
$ alias clean
alias clean='sed -i '\''s/\r//g; s/ /\t/g'\'' ./a
./b
./c
./d
./giga.txt'
Notice how the find was evaluated at alias definition time and pulled in all of the files in my /tmp directory.
To address this issue you want to make sure the find is not evaluated at the time the alias is created.
There are a few ways to do this, one idea being to wrap the find portion of the definition in single quotes, another idea would be keep the current double quotes and just escape the $, eg:
$ alias clean="sed -i 's/\r//g; s/ /\t/g' "'$(find . -maxdepth 1 -type f)'
$ alias clean
alias clean='sed -i '\''s/\r//g; s/ /\t/g'\'' $(find . -maxdepth 1 -type f)'
$ alias alias clean="sed -i 's/\r//g; s/ /\t/g' \$(find . -maxdepth 1 -type f)"
$ alias clean
alias clean='sed -i '\''s/\r//g; s/ /\t/g'\'' $(find . -maxdepth 1 -type f)'
Notice in both cases the alias contains the actual find command instead of the results of evaluating it in the current directory.

Related

How to exclude `.git` then backup and restore folders and files owner and permission?

I prefer to use bash script,because getfacl,setfacl has big problem(bug).
Before I ask this question,I also search a way of pure bash script to backup and restore owner and permission.It's a pity that no one acknowledged perfect answer.
Then I use a easy way to backup and restore owner and permission:
getfacl -R . >permissions.facl
setfacl --restore=permissions.facl
If I want to exclude .git from .,how to do?
Yes you can.
For example, my directory looks like
[root#967dd7743677 test]# ls -la
total 20
drwxr-xr-x 4 root root 4096 Jan 11 06:37 .
drwxr-xr-x 1 root root 4096 Jan 2 11:08 ..
drwxr-xr-x 8 root root 4096 Jan 2 12:56 .git
drwxr-xr-x 2 root root 4096 Jan 11 06:37 1one
-rw-r--r-- 1 root root 19 Jan 2 12:55 testfile
[root#967dd7743677 test]#
by using find you can exclude any directory you want
find . -type f -not -path '*/\.git/*' -exec getfacl -R {} \;
so via -exec we are calling getfacl -R.
and you can redirect the output
find . -type f -not -path '*/\.git/*' -exec getfacl -R {} \; > permissions.facl
Hope it helps.

How to rm directory which name has quotation and question marks on macOS

I have two directory which name are very strange as below:
"?????+ "BizComponent
"?+ "BizComponent
and now I try to remove them but failed. when I type rm -r ' then type tab it give me:
"^J^I^I^I^I+ "BizComponent/BizComponent/
"^J+ "BizComponent/
And then, when I type rm -r "^J^I^I^I^I+ "BizComponent/BizComponent/ it gives me No such file or directory
The problem is that your filename contains a special character, but this is not necessarily the character you see. Example (taken from here):
$ touch zzz yyy $'zzz\nyyy'
$ ls
yyy zzz zzz?yyy
As you see, a filename with a new line was created, but ls printed it as a ?. So how do we remove this?
method 1:
An option available to ls is --quoting-style=shell-escape, this allows you to see how to type the filename for removal (works on files and directories):
$ ls --quoting-style=shell-escape
yyy zzz 'zzz'$'\n''yyy'
$ rm 'zzz'$'\n''yyy'
$ ls
yyy zzz
method 2: The second method is to use the inode number using find (works on files and directories):
$ touch zzz yyy $'zzz\nyyy'
$ ls -li
total 2
3886009 -rw-r--r-- 1 user group 0 Jul 23 12:56 yyy
3886008 -rw-r--r-- 1 user group 0 Jul 23 12:56 zzz
662083 -rw-r--r-- 1 user group 0 Jul 23 12:56 zzz?yyy
$ find . -inum 662083 -delete
$ ls -li
total 1
3886009 -rw-r--r-- 1 user group 0 Jul 23 12:56 yyy
3886008 -rw-r--r-- 1 user group 0 Jul 23 12:56 zzz
Looks like you have some special characters in your directory names. You could use a loop with a glob:
for dir in *BizComponent; do
rm -r -- "$dir"
done
Due to the shell expansion you need to escape the special characters ? + " ^ and space. So to reference your files, you need to remove/reference them likes this: rm -rf \"\?\?\?\?\?+\ \"BizComponent/
rm -rf \"\?\+\ \"BizComponent rm -rf \"\^J\^I\^I\^I\^I+\ \"BizComponent/BizComponent/
and so on.
Why don't you try rm -rf "?????+ BizComponent" | echo $?

Rename file and keep the extension in bash

I have to rename some files that I don't exactly know where they are and keep their extensions.
Ex: files system-2.1.3.war, system-2.1.3.ear, system-2.1.3.ejb
to system.ear, system.war,system.ejb
So I wrote this.
find /DIR1 -name "*.ear" -o -name "*.war" -o -name "*.ejb" \
-exec bash -c 'export var1={}; cp $var1 NEW_NAME${var1: -4}' \;
The problem is: It works only for the last file in the "or list" of "find" command. So if the file is system-2.1.3.ejb, works, for system-2.1.3.war and system-2.1.3.ear don't.
If I change the find to
find /DIR1 -name "*.ejb" -o -name "*.war" -o -name "*.ear"
Notice that *.ear now is the last one, it will work for system-2.1.3.ear and not for the others and so on.
Please help me to fix this.
I know I can create a script to accomplish that but I want a "one line" code.
Rather than embedding the {} in the script, pass it as an argument:
find /DIR1 \( -name "*.ear" -o -name "*.war" -o -name "*.ejb" \) \
-exec sh -c 'ext=${1##*.}; cp "$1" "NEW_NAME.$ext"' _ '{}' \;
Without the \(...\) grouping, -exec only applies to the primary it is implicitly "and"ed with, the previous one.
You can also limit the number of calls to the shell by looping over multiple arguments:
find /DIR1 \( ... \) -exec sh -c 'for f; do ext=${f##*.}; cp "$f" "NEW_NAME.$ext"; done' _ {} +
try this;
find /DIR1 \( -name "*.ear" -o -name "*.war" -o -name "*.ejb" \) -exec bash -c 'export var1={}; cp $var1 NEW_NAME${var1: -4}' \;
or
find ./DIR1/ -regex '.*\(.ear\|.war\|.ejb\)$' -exec bash -c 'export var1={}; cp $var1 NEW_NAME${var1: -4}' \;
Eg;
user#host $ ls -arlt DIR1/
total 76
-rw-rw-r-- 1 user user 0 Oct 21 22:59 system-2.1.3.war
-rw-rw-r-- 1 user user 0 Oct 21 22:59 system-2.1.3.ear
-rw-rw-r-- 1 user user 0 Oct 21 22:59 system-2.1.3.ejb
drwxrwxr-x 2 user user 4096 Oct 21 22:59 .
user#host $ find . \( -name "*.ear" -o -name "*.war" -o -name "*.ejb" \) -exec bash -c 'export var1={}; cp $var1 NEW_NAME${var1: -4}' \;
user#host $ ls -ralt
total 76
-rw-rw-r-- 1 user user 0 Oct 21 22:59 system-2.1.3.war
-rw-rw-r-- 1 user user 0 Oct 21 22:59 system-2.1.3.ear
-rw-rw-r-- 1 user user 0 Oct 21 22:59 system-2.1.3.ejb
drwxrwxrwt 11 root root 69632 Oct 21 23:10 ..
-rw-rw-r-- 1 user user 0 Oct 21 23:10 NEW_NAME.war
-rw-rw-r-- 1 user user 0 Oct 21 23:10 NEW_NAME.ear
-rw-rw-r-- 1 user user 0 Oct 21 23:10 NEW_NAME.ejb
drwxrwxr-x 2 user user 4096 Oct 21 23:10 .
If you have rename utility then you can avoid forking BASH subprocess for each file and also make use of regex feature in find to avoid multiple -name options:
find /DIR1 -regextype awk -regex '.*\.([we]ar|ejb)$' \
-exec rename 's/.*(\.[^.]+)$/system$1/' '{}' +
I'd use a while:
find . -name "*.war" -o -name "*.ejb" -o -name "*.ear" | while read file; do cp $file NEW_NAME${file: -4}; done
Keep in mind that both this and your example are copying the files in the current directory, so if you have more than one *.war, *.ejb or *.ear in your tree, only the last one(s) will be left in the target directory.

List all files older than x days only in current directory

I am new to unix and couldn't get appropriate outcome in other questions.
I want to list only files in current directory which are older than x days. I have below restriction
List only files in current folder which are older than 30
days
Output shouldn't include directories and subdirectories
This should list files similar as "ls" command does
Output should look like file1 file2 file3 ..
I used find . -mtime +30. but this gives files and files in sub-directories as well. I would like to restrict doing search recursively and not to search inside directories.
Thanks a lot in advance !
You can do this:
find ./ -maxdepth 1 -type f -mtime +30 -print
If having problems, do:
find ./ -depth 1 -type f -mtime +30 -print
To add on #Richasantos's answer:
This works perfectly fine
$ find . -maxdepth 1 -type f -mtime +30
Prints:
./file1
./file2
./file3
You can now pipe this to anything you want. Let's say you want to remove all those old files:
$ find . -maxdepth 1 -type f -mtime +30 -print | xargs /bin/rm -f
From man find: ``
If you are piping the output of find into another program and there is the faintest possibility that the files which you are searching for might contain a newline, then you should seriously consider using the -print0 option instead of -print.
So using -print0
$ find . -maxdepth 1 -type f -mtime +30 -print0
Prints (with null characters in between):
./file1./file2./file3
And is used like this to remove those old files:
$ find . -maxdepth 1 -type f -mtime +30 -print0 | xargs -0 /bin/rm -f
You can use find . -maxdepth 1 to exclude subdirectories.
A slightly different spin on this: find is incredibly versatile, you can specify size and time as follows:
This finds you all the logs that are 4 months or older and bigger than 1 meg.
If you remove the + sign, it finds files that are roughly that size.
find /var/log -type f -mtime +120 -size +1M
/var/log/anaconda/journal.log
/var/log/ambari-agent/ambari-alerts.log.23
/var/log/ambari-agent/ambari-alerts.log.22
/var/log/ambari-agent/ambari-alerts.log.24
/var/log/ambari-agent/ambari-alerts.log.25
/var/log/ambari-agent/ambari-alerts.log.21
/var/log/ambari-agent/ambari-alerts.log.20
/var/log/ambari-agent/ambari-alerts.log.19
What's even better, you can feed this into an ls:
find /var/log -type f -mtime +120 -size +1M -print0 | xargs -0 ls -lh
-rw-r--r--. 1 root root 9.6M Oct 1 13:24 /var/log/ambari-agent/ambari-alerts.log.19
-rw-r--r--. 1 root root 9.6M Sep 27 07:44 /var/log/ambari-agent/ambari-alerts.log.20
-rw-r--r--. 1 root root 9.6M Sep 22 03:32 /var/log/ambari-agent/ambari-alerts.log.21
-rw-r--r--. 1 root root 9.6M Sep 16 23:23 /var/log/ambari-agent/ambari-alerts.log.22
-rw-r--r--. 1 root root 9.6M Sep 11 19:12 /var/log/ambari-agent/ambari-alerts.log.23
-rw-r--r--. 1 root root 9.6M Sep 6 15:02 /var/log/ambari-agent/ambari-alerts.log.24
-rw-r--r--. 1 root root 9.6M Sep 1 10:51 /var/log/ambari-agent/ambari-alerts.log.25
-rw-------. 1 root root 1.8M Mar 11 2019 /var/log/anaconda/journal.log

grep ls output across tabs

If I ls -l in a directory and get:
-rwxr-x--- 1 user1 admin 0 8 Aug 2012 file.txt
-rwxr-x--- 1 user1 admin 1733480 26 Jul 2012 Archive.pax.gz
drwxr-x---# 7 user1 admin 238 31 Jul 2012 Mac Shots
-rwxr-x---# 1 user3 admin 598445 31 Jul 2012 Mac Shots.zip
-rwxr-x---# 1 user1 admin 380 6 Jul 2012 an.sh
-rwxr-x--- 1 user2 admin 14 30 Jun 2012 analystName.txt
-rwxr-x--- 1 user1 admin 36 8 Aug 2012 apple.txt
drwxr-x---# 7 user1 admin 238 31 Jul 2012 iPad Shots
-rwxr-x---# 1 user1 admin 7372367 31 Jul 2012 iPad Shots.zip
-rwxr-x--- 1 user2 admin 109 30 Jun 2012 test.txt
drwxr-x--- 3 user1 admin 102 26 Jul 2012 usr
but want to list only the files owned by "user1" which were modified in "Aug" to get
-rwxr-x--- 1 user1 admin 0 8 Aug 2012 file.txt
-rwxr-x--- 1 user1 admin 36 8 Aug 2012 apple.txt
What is the best method?
Parsing ls output is never a good and reliable solution. ls is a tool for interactively looking at file information. Its output is formatted for humans and will cause bugs in scripts. Use globs or find instead. Understand why: http://mywiki.wooledge.org/ParsingLs
Instead, you can try :
find . -type f -user 'user1' -maxdepth 1
or
find . -type f -printf '%u %f\n' -maxdepth 1 # if you want to show the username
or
stat -c '%U %f' * | cut -d" " -f2-
See
man find
man stat
Or you can be more explicit, since Michael's grep would also find a file owned by user1 namedd 'August iPad Shots' no matter when it was modified:
ls -l | awk '($3=="user1" && $7=="Aug")'
I think the safest way to do it is like this :
touch --date "2012-08-01" /tmp/start
touch --date "2012-09-01" /tmp/stop
find . -maxdepth 1 -type f -user user1 -newer /tmp/start -not -newer /tmp/stop -print0 | xargs -0 ls -l {}
rm /tmp/start /tmp/stop
Or as a one liner
touch --date "2012-08-01" /tmp/start; touch --date "2012-09-01" /tmp/stop; find . -maxdepth 1 -type f -user user1 -newer /tmp/start -not -newer /tmp/stop -print0 | xargs -0 ls -l {}; rm /tmp/start /tmp/stop
Advantages:
You don't parse ls
It works for filenames with Aug in them
Disadvantages
It is a bit long
Explanation:
-maxdepth 1: restricts the results to the current directory
-type f: restricts the results to files
-user user1: resttrings the results to files that belong to user1
-newer /tmp/start: restring the results to files newer than /tmp/start, which was created with the desired date
-not -newer /tmp/stop: restring the results to files not newer than /tmp/stop, which was created with the desired date
-print0: so it can handle filenames with newlines in their name!
How about ls -l | grep user1 | grep Aug?
Or you can combine the regexp: ls -l | grep 'user1.*Aug'

Resources