Modify the bash script so that after checking the type of the entity, it also lists the details of the input entity - linux

PASSED=$1
if [ -f $PASSED ]; then
echo "$PASSED is a file"
ls -l $PASSED
elif [ -d $PASSED ]; then
echo "$PASSED is a directory"
ls -l $PASSED
else
"$PASSED is invalid"
fi
At the terminal when I push a file input, say demo.sh, the output is correctly printed as:
"demo.sh is a file"
rwxr-xr-x 1 system system 12 Jan 16 03:12 26 14:47 demo.sh
but for a directory, say cloud, it gives:
cloud is a directory
total 0
What should I do to rectify this?
enter image description here

As shown from the man pages for ls:
-d, --directory
list directories themselves, not their contents
With your current implementation, as you are using just -l with your directories, ls will show the contents of the directory. To list the properties of the directory only, use -d in addition to -l and so:
ls -ld $PASSED

Related

Differentiation on whether directory exists and permission error

Looking for a very simple way to check whether a file/directory exists while evaluating user permissions, returning different (code) errors:
There is command test that checks for permissions but fails to provide a better return code for case where file does not exist:
$ test -r /tmp/; echo $? # 0
$ test -r /tmp/root/; echo $? # 1
$ test -r /tmp/missing/; echo $? # 1
I am looking for something similar to ls where I get a different message for different errors:
$ ls /tmp/root
ls: root: Permission denied
$ ls /tmp/missing
ls: /tmp/missing: No such file or directory
I like the differentiation but the error code is 1 in both. To properly handle each error, I have to parse stderr which is honestly a very inelegant solution.
Isn't there a better and graceful way of doing this?
Something close to a pythonic way looks something like this:
import os
os.listdir("/tmp/root/dir/") # raises PermissionError
os.listdir("/tmp/foo/") # raises FileNotFoundError
Read the manual some more. There's also -d to specifically check whether the target is a directory, and a slew of other predicates to check for symlinks, device nodes, etc.
testthing () {
if ! [[ -e "$1" ]]; then
echo "$1: not found" >&2
return 2
elif ! [[ -d "$1" ]]; then
echo "$1: not a directory" >&2
return 4
elif ! [[ -r "$1" ]]; then
echo "$1: permission denied" >&2
return 8
fi
return 0
}
Usage:
testthing "/root/no/such/directory"
Notice that [[ is a Bash built-in which is somewhat more robust and versatile than the legacy [ aka test.
It's hard to predict what the priorities should be, but if you want the comparisons in a different order, by all means go for it. It is unavoidable that the shell cannot correctly tell the precise status of a directory entry when it lacks read access to the parent directory. Maybe solve this from the caller by examining the existence and permissions of every entry in the path, starting from the root directory.
The shell and standard utilities do not provide a command that does everything you seem to want:
with a single command execution,
terminate with an exit status that reports in detail on the existence and accessability of a given path,
contextualized for the current user,
accurately even in the event that a directory prior to the last path element is untraversable (note: you cannot have this one no matter what),
(maybe) correctly for both directories and regular files.
The Python os.listdir() doesn't do all of that either, even if you exclude the applicability to regular files and traversing untraversible directories, and reinterpret what "exit status" means. However, os.listdir() and ls both do demonstrate a good and useful pattern: to attempt a desired operation and deal with any failure that results, instead of trying to predict what would happen if you tried it.
Moreover, it's unclear why you want what you say you want. The main reason I can think of for wanting information about the reason for a file-access failure is user messaging, but in most cases you get that for free by just trying to perform the wanted access. If you take that out of the picture, then it rarely matters why a given path is inaccessible. Any way around, you need to either switch to an alternative or fail.
If you nevertheless really do want something that does as much as possible of the above, then you probably will have to roll your own. Since you expressed concern for efficiency in some of your comments, you'll probably want to do that in C.
Given:
$ ls -l
total 0
-rw-r--r-- 1 andrew wheel 0 Mar 22 12:01 can_read
---xr-x--x 1 andrew wheel 0 Mar 22 12:01 root
drwxr-xr-x 2 andrew wheel 64 Mar 22 13:09 sub
Note that permissions are by user for the first three, group for the second three and other or world for the last three.
Permission Denied error is from 1) Trying to read or write a file without that appropriate permission bit set for your user or group or 2) Tying to navigate to a directory without x set or 3) Trying to execute a file without appropriate permission.
You can test if a file is readable or not for the user with the -r test:
$ [[ -r root ]] && echo 'readable' || echo 'not readable'
not readable
So if you only are concerned with user permissions, -r, -w and -x test are what you are looking for.
If you want to test permissions generally, you need to use stat.
Here is a simple example with that same directory:
#!/bin/bash
arr=(can_read root sub missing)
for fn in "${arr[#]}"; do
if [[ -e "$fn" ]]
then
p=( $(stat -f "%SHp %SMp %SLp" "$fn") )
printf "File:\t%s\nUser:\t%s\nGroup:\t%s\nWorld:\t%s\nType:\t%s\n\n" "$fn" "${p[#]}" "$(stat -f "%HT" "$fn")"
else
echo "\"$fn\" does not exist"
fi
done
Prints:
File: can_read
User: rw-
Group: r--
World: r--
Type: Regular File
File: root
User: --x
Group: r-x
World: --x
Type: Regular File
File: sub
User: rwx
Group: r-x
World: r-x
Type: Directory
"missing" does not exist
Alternatively, you can grab these values directly from the drwxr-xr-x type data with:
for fn in "${arr[#]}"; do
if [[ -e "$fn" ]]
then
p=$(stat -f "%Sp" "$fn")
typ="${p:0:1}"
user="${p:1:3}"
grp="${p:4:3}"
wrld="${p:7:3}"
else
echo "\"$fn\" does not exist"
fi
done
In either case, you can then test the individual permissions with either Bash string functions, Bash regex, or get the octal equivalents and use bit masks.
Here is an example:
for fn in "${arr[#]}"; do
if [[ -e "$fn" ]]
then
p=$(stat -f "%Sp" "$fn")
user="${p:1:3}"
ty="$(stat -f "%HT" "$fn")"
printf "%s \"$fn\" is:\n" "$ty"
[[ $user =~ 'r' ]] && echo " readable" || echo " not readable"
[[ $user =~ 'w' ]] && echo " writeable" || echo " not writeable"
[[ $user =~ 'x' ]] && echo " executable" || echo " not executable"
else
echo "\"$fn\" does not exist"
fi
done
Prints:
Regular File "can_read" is:
readable
writeable
not executable
Regular File "root" is:
not readable
not writeable
executable
Directory "sub" is:
readable
writeable
executable
"missing" does not exist
(Note: stat tends to be platform specific. This is BSD and Linux will have different format strings...)
An example of use.
for d in 1 2 3; do
if [[ -e $d ]]; then printf "%s exists" $d
[[ -r $d ]] && echo " and is readable" || echo " but is not readable"
else echo "$d does not exist"
fi
stat -c "%A %n" $d
done
1 exists and is readable
drwxr-xr-x 1
2 exists but is not readable
d--------- 2
3 does not exist
stat: cannot stat ‘3’: No such file or directory
If you absolutely have to have it in one step with differentiated exit codes, write a function. (a/b is there and has accessible permissions.)
$: stat -c "%A %n" ? . a/b # note there is no directory named 3
drwxr-xr-x 1
drwxr-xr-x 2
drwxr-xr-x a
drwxrwxrwt .
drwxr-xr-x a/b
$: doubletest() { if [[ -e "$1" ]]; then [[ -r "$1" ]] && return 0 || return 2; else return 1; fi; }
$: result=( "exists and is readable" "does not exist" "exists but is unreadable" ) # EDITED - apologies, these were out of order
$: for d in . a a/b 1 2 3; do doubletest $d; echo "$d ${result[$?]}"; done
. exists and is readable
a exists and is readable
a/b exists and is readable
1 exists and is readable
2 exists and is readable
3 does not exist
$: chmod 0000 a
$: for d in . a a/b 1 2 3; do doubletest $d; echo "$d ${result[$?]}"; done
. exists and is readable
a exists but is unreadable
a/b does not exist
1 exists and is readable
2 exists but is unreadable
3 does not exist
"does not exist" for a/b is because a does not have read permissions, so there is no way for any tool to know what does or does not exist in that directory short of using root privileges.
$ sudo stat -c "%A %n" ? . a/b # sudo shows a/b
drwxr-xr-x 1
drwxr-xr-x 2
d--------- a
drwxrwxrwt .
drwxr-xr-x a/b
In that case your problem isn't the tool, it's that the tool can't do what you are asking it to do.

Linux shell script to know if the directory (or file) has 777 permission

We give the upmost permission to a file or directory, using this command:
sudo chmod -R 777 directory
Now I want to know if this command is already executed for a given directory.
I know I can use -r for read, -w for write, and -x for execution, in [ test ] blocks.
But I want to know two things:
Is it also a directory?
Does it have those permissions for everyone?
How can I get that info?
Update
Based on #Barmar comment, I came up with this. But it's not working:
if [ stat /Temp | grep -oP "(?<=Access: \()[^)]*" == '' ]; then
echo '/Temp folder has full access'
else
sudo chmod -R 777 /Temp
fi
This command works though:
stat /Temp | grep -oP "(?<=Access: \()[^)]*"
# prints => 0777/drwxrwxrwx
How should I fix the syntax error of my if-else statement?
You don't need to process the output of stat with grep; you can ask stat to only produce the specific information you want. See the man page regarding the --format option. We can for example write:
# ask stat for the file type and mode, and put those values into $1
# and $2
set -- $(stat --format '%F %a' /Temp)
if [[ $1 == directory ]]; then
if [[ $2 == 777 ]]; then
echo "/Temp folder has full access"
else
sudo chmod -R 777 /Temp
fi
else
echo "ERROR: /Temp is not a directory!" >&2
fi
A simple example:
#!/bin/bash
function setfullperm(){
[ -d $1 ] && \
(
[ "$(stat --format '%a' $1)" == "777" ] && \
echo "Full permissions are applied." || \
( echo "Setting full permissions" && sudo chmod -R 777 $1 )
) || \
( echo "$1 is not a directory !" && mkdir $1 && setfullperm $1 )
}
export setfullperm
Source the script:
$ source example.sh
Set full permissions (777) on any directory, it tests if the directory exists in the first place, if not it will create it and set the permissions.
It will export the function setfullperm to the shell so you can run it:
>$ setfullperm ali
ali is not a directory !
mkdir: created directory 'ali'
Setting full permissions
>$ setfullperm ali
Full permissions are applied.
If using zsh (But not other shells), you can do it with just a glob pattern:
setopt extended_glob null_glob
if [[ -n /Temp(#q/f777) ]]; then
echo '/Temp folder has full access'
else
sudo chmod -R 777 /Temp
fi
The pattern /Temp(#q/f777) will, with the null_glob and extended_glob options set, expand to an empty string if /Temp is anything but a directory with the exact octal permissions 0777 (And to /Temp if the criteria are met). For more details, see Glob Qualifiers in the zsh manual.
I don't recommend using stat for this. Though widespread, stat isn't POSIX, which means there's no guarantee that your script will work in the future or work on other platforms. If you're writing scripts for a production environment, I'd urge you to consider a different approach.
You're better off using ls(1)'s -l option and passing the file as an argument. From there you can use cut(1)'s -c option to grab the file mode flags.
Get file type:
ls -l <file> | cut -c1
Also, don't forget about test's -d operator, which tests if a file is a directory.
Get owner permissions:
ls -l <file> | cut -c2-4
and so on.
This approach is POSIX compliant and it avoids the shortcomings of using stat.

Testing a file in a tar archive

I've been manipulating a tar file and I would like to test if a file exists before extracting it
Let's say I have an tar file called Archive.Tar and after entering
tar -tvf Archive.Tar
I get:
-rwxrwxrwx guy/root 1502 2013-10-02 20:43 Directory/File
-rwxrwxrwx guy/root 494 2013-10-02 20:43 Dir/SubDir/Text
drwxrwxrwx guy/root 0 2013-10-02 20:43 Directory
I want to extract Text into my Working directory, but I want to be sure that it's actually a file by doing this:
if [ -f Dir/Sub/Text ]
then
echo "OK"
else
echo "KO"
fi
The result of this test is always KO and I really don't understand why, any suggestions?
Tested with BSD and GNU versions of tar,
in the output of tar tf,
entries that are directories end with /.
So to test if Dir/Sub/Text is a file or directory in the archive,
you can simply grep with full line matching:
if tar tf Archive.Tar | grep -x Dir/Sub/Text >/dev/null
then
echo "OK"
else
echo "KO"
fi
If the archive contains Dir/SubDir/Text/, then Dir/SubDir/Text is a directory, and the grep will not match, so KO will be printed.
If the archive contains Dir/SubDir/Text without a trailing /,
then Dir/SubDir/Text is a file and the grep will match,
so OK will be printed.
if [ ! -d Dir/Sub/Text ]
then
echo "OK"
else
echo "KO"
fi
will return KO only if a directory Text exists and be ok if it's a file or does not exist (or to be precise also OK if it would be a symlink).
This might be a solution,
tar -tvf Archive.Tar | grep Dir/Sub/Text
This will let you know if it find the file.

Linux Bash Shell Script: How to "ls" a directory and check some output string?

I'm just newbie to Linux Shell Scripting. What i need to know is, normally in the command line, we simply use:
# ls /var/log
audit ConsoleKit cups maillog messages ntpstats secure-20130616 spooler-20130623 vsftpd.log-20130616
boot.log cron dmesg maillog-20130616 messages-20130616 prelink secure-20130623 spooler-20130701 vsftpd.log-20130623
... . . . ..
Then
# ls /var/aaaaaaaaaaaaaaa
ls: cannot access /var/aaaaaaaaaaaaaaa: No such file or directory
So with the Shell Script:
How can i run the command: # ls /var/aaaaaaaaa
and then detect if there is the output string ls: cannot access or not?
Note: You may ask me whether i want to detect just the Failure. Or the output string. I'm very keen to know the both way. Thank you.
To check for a directory:
if [ ! -d '/var/aaaaaaa' ]; then
echo 'no dir!'
fi
For file:
if [ ! -f '/var/aaaaaaa' ]; then
echo 'no file!'
fi
To check output:
if ls '/var/aaaaaaa' 2>&1 | grep 'No such'; then
echo 'no such';
fi
To check when ls fails:
if ! ls '/var/aaaaaaa' &> /dev/null; then
echo 'failed'
fi

rsync prints "skipping non-regular file" for what appears to be a regular directory

I back up my files using rsync. Right after a sync, I ran it expecting to see nothing, but instead it looked like it was skipping directories. I've (obviously) changed names, but I believe I've still captured all the information I could. What's happening here?
$ ls -l /source/backup/myfiles
drwxr-xr-x 2 me me 4096 2010-10-03 14:00 foo
drwxr-xr-x 2 me me 4096 2011-08-03 23:49 bar
drwxr-xr-x 2 me me 4096 2011-08-18 18:58 baz
$ ls -l /destination/backup/myfiles
drwxr-xr-x 2 me me 4096 2010-10-03 14:00 foo
drwxr-xr-x 2 me me 4096 2011-08-03 23:49 bar
drwxr-xr-x 2 me me 4096 2011-08-18 18:58 baz
$ file /source/backup/myfiles/foo
/source/backup/myfiles/foo/: directory
Then I sync (expecting no changes):
$ rsync -rtvp /source/backup /destination
sending incremental file list
backup/myfiles
skipping non-regular file "backup/myfiles/foo"
skipping non-regular file "backup/myfiles/bar"
And here's the weird part:
$ echo 'hi' > /source/backup/myfiles/foo/test
$ rsync -rtvp /source/backup /destination
sending incremental file list
backup/myfiles
backup/myfiles/foo
backup/myfiles/foo/test
skipping non-regular file "backup/myfiles/foo"
skipping non-regular file "backup/myfiles/bar"
So it worked:
$ ls -l /source/backup/myfiles/foo
-rw-r--r-- 1 me me 3126091 2010-06-15 22:22 IMGP1856.JPG
-rw-r--r-- 1 me me 3473038 2010-06-15 22:30 P1010615.JPG
-rw-r--r-- 1 me me 3 2011-08-24 13:53 test
$ ls -l /destination/backup/myfiles/foo
-rw-r--r-- 1 me me 3126091 2010-06-15 22:22 IMGP1856.JPG
-rw-r--r-- 1 me me 3473038 2010-06-15 22:30 P1010615.JPG
-rw-r--r-- 1 me me 3 2011-08-24 13:53 test
but still:
$ rsync -rtvp /source/backup /destination
sending incremental file list
backup/myfiles
skipping non-regular file "backup/myfiles/foo"
skipping non-regular file "backup/myfiles/bar"
Other notes:
My actual directories "foo" and "bar" do have spaces, but no other strange characters. Other directories have spaces and have no problem. I 'stat'-ed and saw no differences between the directories that don't rsync and the ones that do.
If you need more information, just ask.
Are you absolutely sure those individual files are not symbolic links?
Rsync has a few useful flags such as -l which will "copy symlinks as symlinks". Adding -l to your command:
rsync -rtvpl /source/backup /destination
I believe symlinks are skipped by default because they can be a security risk. Check the man page or --help for more info on this:
rsync --help | grep link
To verify these are symbolic links or pro-actively to find symbolic links you can use file or find:
$ file /path/to/file
/path/to/file: symbolic link to `/path/file`
$ find /path -type l
/path/to/file
Are you absolutely sure that it's not a symbolic link directory?
try a:
file /source/backup/myfiles/foo
to make sure it's a directory
Also, it could very well be a loopback mount
try
mount
and make sure that /source/backup/myfiles/foo is not listed.
You should try the below command, most probably it will work for you:
rsync -ravz /source/backup /destination
You can try the following, it will work
rsync -rtvp /source/backup /destination
I personally always use this syntax in my script and works a treat to backup the entire system (skipping sys/* & proc/* nfs4/*)
sudo rsync --delete --stats --exclude-from $EXCLUDE -rlptgoDv / $TARGET/ | tee -a $LOG
Here is my script run by root's cron daily:
#!/bin/bash
#
NFS="/nfs4"
HOSTNAME=`hostname`
TIMESTAMP=`date "+%Y%m%d_%H%M%S"`
EXCLUDE="/home/gcclinux/Backups/root-rsync.excludes"
TARGET="${NFS}/${HOSTNAME}/SYS"
LOGDIR="${NFS}/${HOSTNAME}/SYS-LOG"
CMD=`/usr/bin/stat -f -L -c %T ${NFS}`
## CHECK IF NFS IS MOUNTED...
if [[ ! $CMD == "nfs" ]];then
echo "NFS NOT MOUNTED"
exit 1
fi
## CHECK IF LOG DIRECTORY EXIST
if [ ! -d "$LOGDIR" ]; then
/bin/mkdir -p $LOGDIR
fi
## CREATE LOG HEADER
LOG=$LOGDIR/"rsync_result."$TIMESTAMP".txt"
echo "-------------------------------------------------------" | tee -a $LOG
echo `date` | tee -a $LOG
echo "" | tee -a $LOG
## START RUNNING BACKUP
/usr/bin/rsync --delete --stats --exclude-from $EXCLUDE -rlptgoDv / $TARGET/ | tee -a $LOG
In some cases just copy file to another location (like home) then try again

Resources