Check if two files are on the same volume in bash - macos

Is there a way to check if two files are on the same volume in bash on Mac OS X?
Or, equivalently, to check if a given file is on the boot volume?
I've tried using ln and checking to see if it fails, but sometimes ln fails for reasons other than the cross-device link error.
I also tried using a function that prints the path of a file and then checking to see if that path contains /Volumes/ as a prefix, but not all of my remote volumes get mounted to /Volumes/.
Is there another way?

You are asking if two files are on the same filesystem. The canonical way of checking this is to call the stat() system call on the two files and check if they have the same st_dev value, where st_dev identifies the device on which the file resides.
You can use the stat command in bash to perform this test:
device=$(stat -f '%d' /path/to/file)
So, to check if two files are on the same filesystem:
dev1=$(stat -f '%d' /path/to/file1)
dev2=$(stat -f '%d' /path/to/file2)
if [ "$dev1" = "$dev2" ]; then
echo "file1 and file2 are on the same filesystem"
fi
The above works under OS X; the same check can be performed on Linux, but the stat command requires -c or --format instead of -f.

With df:
f1="$(df -P /path/to/file1.txt | awk 'NR!=1 {print $1}')"
f2="$(df -P /path/to/file2.txt | awk 'NR!=1 {print $1}')"
if [[ "$f1" = "$f2" ]]; then
echo "same filesystem"
else
echo "different filesystem"
fi

Related

bash - find duplicate file in directory and rename

I have a directory that has thousands of files in it with various extensions. I also have a drop location where users drop files to be migrated to this directory. I'm looking for a script that will scan the target directory for a duplicate file name, if found, rename the file in the drop folder, then move it to the target directory.
Example:
/target/file.doc
/drop/file.doc
Script will rename file.doc to file1.doc then move it to /target/.
It needs to maintain the file extension too.
for fil in /drop/*
do
test -f "/target/$fil"
if [ "$?" = 0 ]
then
suff=$(awk -F\. '{ print "."$NF }' <<<$fil)
bdot=$(basename -s $suff $fil)
mv "/drop/$fil" "/drop/${bdot}1$suff"
cp "/drop/${bdot}1.$suff" "/target/${bdot}1$suff"
fi
done
Take each file in the drop directory and check it is existing the /target using test -e. If it does then move (rename) and then copy.
You have to take a bit more care than simply checking if a file exists before moving in order to provide a flexible solution that can handle files with or without extensions. You also may want to provide a way of forming duplicate filenames that preserves sort order. e.g. if file.txt already exists, you may want to use file_001.txt as the duplicate in target rather than file1.txt as when you reach 10 you will no longer have a canonical sort by filename.
Also, you never want to iterate with for i in $(ls dir) that is wrought with pitfalls. See Bash Pitfalls No. 1
Putting those pieces together, and including detail in the comments below, you could do something similar to the following and have a reasonable flexible solution allowing you to specify only the filename.ext to move or /path/to/drop/filename.ext. You must specify the drop and target directories in the script to meet your circumstance., e.g.
#!/bin/bash
tgt=target ## set target and drop directories as required
drp=drop
declare -i cnt=1 ## counter for filename_$cnt
test -z "$1" && { ## validate one argument given
printf "error: insufficient input\nusage: %s filename\n" "${0##*/}"
exit 1
}
test -w "$1" || test -w "$drp/$1" || { ## validate valid filename is writeable
printf "error: file not found or lack permission to move '%s'.\n" "$1"
exit 1
}
fn="${1##*/}" ## strip any path info from filename
if test "$1" != "${1%.*}" ; then
ext="${fn##*.}" ## get file extension
fnwoe="${fn%."$ext"}" ## get filename without extension
test "$fnwoe" = '' && ext= ## was a dotfile, reset ext
fi
vfn="$fn" ## set valid filename = filename
## form valid filename e.g. "$fn_001.$ext" if duplicate found
while test -e "$tgt/$vfn"; do
if test -n "$ext" ## did we have have an extension?
then
printf -v vfn "%s_%03d.%s" "$fnwoe" "$((cnt++))" "$ext"
else
printf -v vfn "%s_%03d" "$fn" "$((cnt++))"
fi
done
mv "$drp/$fn" "$tgt/$vfn" ## move file under non-conflicting name
Example drop and target
$ ls -1 drop
file
file.txt
$ ls -1 target
file.txt
file_001.txt
file_002.txt
Example Use
$ bash mvdrop.sh file
$ bash mvdrop.sh drop/file.txt
Resulting drop and target
$ ls -1 drop
$ ls -1 target
file
file.txt
file_001.txt
file_002.txt
file_003.txt
This will test to see if it exists, preserve the extension (along with any structure before the extension such as in the case of FILE.tar.gz), and move it to the target directory.
#!/bin/bash
TARGET="\target\"
DROP="\drop\"
for F in `ls $DROP`; do
if [[ -f $TARGET$F ]]; then
EXT=`echo $F | awk -F "." '{print $NF}'`
PRE=`echo $F | awk -F "." '{$NF="";print $0}' | sed -e 's/ $//g;s/ /./g'`
mv $DROP$F $DROP$PRE"1".$EXT
F=$PRE"1".$EXT
fi
mv $DROP$F $TARGET
done
Additionally you may want to do come restricting in the ls command, so that you aren't copying entire directories.
Display only regular files (no directories or symbolic links)
ls -p $DROP | grep -v /

How to check if a file exists in a shell script

I'd like to write a shell script which checks if a certain file, archived_sensor_data.json, exists, and if so, deletes it. Following http://www.cyberciti.biz/tips/find-out-if-file-exists-with-conditional-expressions.html, I've tried the following:
[-e archived_sensor_data.json] && rm archived_sensor_data.json
However, this throws an error
[-e: command not found
when I try to run the resulting test_controller script using the ./test_controller command. What is wrong with the code?
You're missing a required space between the bracket and -e:
#!/bin/bash
if [ -e x.txt ]
then
echo "ok"
else
echo "nok"
fi
Here is an alternative method using ls:
(ls x.txt && echo yes) || echo no
If you want to hide any output from ls so you only see yes or no, redirect stdout and stderr to /dev/null:
(ls x.txt >> /dev/null 2>&1 && echo yes) || echo no
The backdrop to my solution recommendation is the story of a friend who, well into the second week of
his first job, wiped half a build-server clean. So the basic task is to figure out if a file exists,
and if so, let's delete it. But there are a few treacherous rapids on this river:
Everything is a file.
Scripts have real power only if they solve general tasks
To be general, we use variables
We often use -f force in scripts to avoid manual intervention
And also love -r recursive to make sure we create, copy and destroy in a timely fashion.
Consider the following scenario:
We have the file we want to delete: filesexists.json
This filename is stored in a variable
<host>:~/Documents/thisfolderexists filevariable="filesexists.json"
We also hava a path variable to make things really flexible
<host>:~/Documents/thisfolderexists pathtofile=".."
<host>:~/Documents/thisfolderexists ls $pathtofile
filesexists.json history20170728 SE-Data-API.pem thisfolderexists
So let's see if -e does what it is supposed to. Does the files exist?
<host>:~/Documents/thisfolderexists [ -e $pathtofile/$filevariable ]; echo $?
0
It does. Magic.
However, what would happen, if the file variable got accidentally be evaluated to nuffin'
<host>:~/Documents/thisfolderexists filevariable=""
<host>:~/Documents/thisfolderexists [ -e $pathtofile/$filevariable ]; echo $?
0
What? It is supposed to return with an error... And this is the beginning of the story how that entire
folder got deleted by accident
An alternative could be to test specifically for what we understand to be a 'file'
<host>:~/Documents/thisfolderexists filevariable="filesexists.json"
<host>:~/Documents/thisfolderexists test -f $pathtofile/$filevariable; echo $?
0
So the file exists...
<host>:~/Documents/thisfolderexists filevariable=""
<host>:~/Documents/thisfolderexists test -f $pathtofile/$filevariable; echo $?
1
So this is not a file and maybe, we do not want to delete that entire directory
man test has the following to say:
-b FILE
FILE exists and is block special
-c FILE
FILE exists and is character special
-d FILE
FILE exists and is a directory
-e FILE
FILE exists
-f FILE
FILE exists and is a regular file
...
-h FILE
FILE exists and is a symbolic link (same as -L)
Internally, the rm command must test for file existence anyway,
so why add another test? Just issue
rm filename
and it will be gone after that, whether it was there or not.
Use rm -f is you don't want any messages about non-existent files.
If you need to take some action if the file does NOT exist, then you must test for that yourself. Based on your example code, this is not the case in this instance.
If you're using a NFS, "test" is a better solution, because you can add a timeout to it, in case your NFS is down:
time timeout 3 test -f
/nfs/my_nfs_is_currently_down
real 0m3.004s <<== timeout is taken into account
user 0m0.001s
sys 0m0.004s
echo $?
124 <= 124 means the timeout has been reached
A "[ -e my_file ]" construct will freeze until the NFS is functional again:
if [ -e /nfs/my_nfs_is_currently_down ]; then echo "ok" else echo "ko" ; fi
<no answer from the system, my session is "frozen">
You could also uses stat :
stat /
File: /
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: fd01h/64769d Inode: 2 Links: 26
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2009-01-01 02:00:00.000000000 +0200
Modify: 2009-01-01 02:00:00.000000000 +0200
Change: 2009-01-01 02:00:00.000000000 +0200
Birth: -
On a path that doesn't exist, you will get:
stat /aaa
stat: cannot stat '/aaa': No such file or directory

Bash: How to enforce output to one line?

I wrote the following Nagios check, which checks /etc/fstab for mounts and by using df checks if they are mounted properly:
#!/bin/bash
# Check mounts based on /etc/fstab
grep="/bin/grep"
awk="/bin/awk"
df="/bin/df"
mounts=$($grep nfs /etc/fstab | $awk '{print $2}')
# Check if mounts exist
for mount in $mounts; do
$df | $grep $mount &>/dev/null
if [ "$?" -eq "0" ]; then
msg="Mount $mount is mounted!"
else
msg="Mount $mount is not mounted!"
fi
echo $msg
done
When I run the check it returns a proper result:
[root#nyproxy5 ~]# ./check_mount.sh
Mount /proxy_logs is mounted!
Mount /proxy_dump is mounted!
Mount /sync_logs is mounted!
[root#nyproxy5 ~]#
But I want the output of the script to be 1 line rather than 3 lines, how can it be achieved?
I realize that the way the script is written at the moment doesn't allow it, even the "Mount X is mounted" message should be changed, but I'm having a hard time with the logic.
Thanks in advance
Change echo $msg to echo -n $msg
-n option will avoid printing newline
There is a general solution in case of some complicated command. Newlines or any other character could be removed this way:
./script.sh | tr -d '\n'
Or a win32 executable in cygwin:
./asd.exe | tr -d '\r\n'

How to check if filepath is mounted in OS X using bash?

How do I perform the mount only if it has not already been mounted?
This is on OS X 10.9 and this is what I have currently:
#!/bin/bash
# Local mount point
LOCALMOUNTPOINT="/folder/share"
# Perform the mount if it does not already exist
if ...
then
/sbin/mount -t smbfs //user:password#serveraddress/share $LOCALMOUNTPOINT
else
echo "Already mounted"
fi
While #hd1's answer gives you whether the file exists, it does not necessary mean that the directory is mounted or not. It is possible that the file happen to exist if you use this script for different machines or use different mount points. I would suggest this
LOCALMOUNTPOINT="/folder/share"
if mount | grep "on $LOCALMOUNTPOINT" > /dev/null; then
echo "mounted"
else
echo "not mounted"
fi
Note that I include "on" in grep statement based on what mount command outputs in my machine. You said you use MacOS so it should work, but depending on what mount command outputs, you may need to modify the code above.
This is what I use in my shell scripts on OS X 10.7.5
df | awk '{print $6}' | grep -Ex "/Volumes/myvolume"
For OS X 10.10 Yosemite I have to change to:
df | awk '{print $9}' | grep -Ex "/Volumes/myvolume"
For me from this solution in stackoverflow_QA_Check if a directory exists in a shell script
[ -d /Volumes/ ] && echo "Already mounted /Volumes/ in OS X" || echo "Not mounted"
to use that
# 2 lines
$LOCALMOUNTPOINT="/folder/share" ;
[ -d $LOCALMOUNTPOINT ] && echo "Already mounted $LOCALMOUNTPOINT in OS X" || /sbin/mount -t smbfs //user:password#serveraddress/share $LOCALMOUNTPOINT
Here is Stackoverflow_QA_how-can-i-mount-an-smb-share-from-the-command-line
additional link about smb which I don't know well
# Here is my solution
$LOCALMOUNTPOINT="/folder/share" ;
[ ! -d $LOCALMOUNTPOINT ] && mkdir $LOCALMOUNTPOINT && /sbin/mount -t smbfs //user:password#serveraddress/share $LOCALMOUNTPOINT || echo already mounted $LOCALMOUNTPOINT
I have to make new folder for my SMOOTH use before mounting that.
so I added mkdir $LOCALMOUNTPOINT
Thank you I learned a lot with this case.
Have A nice day!!! ;)

How can I determine the volume name of CD/DVD in bash?

I'm trying to figure out how to get a bash script to automatically determine the path to a CD/DVD in order to process it. Running a Mac (10.7.4) the disk shows up at:
/Volumes/[Volume_name]
Since the volume name changes depending on the disk, I'm having to input that part manually. The operating system obviously knows it's a CD/DVD because of the way the controls work. Is it possible for bash to use whatever the OS uses to determine there is a CD/DVD and provide the path to it?
I use drutil.
drutil uses the DiscRecording framework to interact with attached burning devices.
#!/bin/bash
id=$(drutil status |grep -m1 -o '/dev/disk[0-9]*')
if [ -z "$id" ]; then
echo "No Media Inserted"
else
df | grep "$id" |grep -o /Volumes.*
fi
Given a UNIX block device name, diskutil info's output is easier to parse than mount's. For instance, this
function get_disk_mountpoint () {
diskutil info $1 | perl -ne 'print "$1\n" if /^ Mount Point: +(.*)/';
}
works. Trouble is, OS X also dynamically assigns /dev/disk? devices to removable media, so you still need something like
function get_optical_mountpoints () {
for i in $(diskutil list | egrep ^/); do
if diskutil info $i | egrep -q '^ Optical Drive Type:' ; then
get_disk_mountpoint $i
fi
done
}
to list the mount points for optical drives specifically.
If a disc is mounted you can use mount to view where it's mounted.
Putting together the pieces from above, I think this will do what you want:
get_cd_volume() {
local rc=1
for disk in $(diskutil list | grep ^/); do
if diskutil info "$disk" | grep -q Optical; then
df | sed -ne "s,^$disk.*\(/Volumes.*\)$,\1,p"
rc=0
fi
done
if (( rc )); then
echo >&2 "No volume mounted."
fi
return $rc
}

Resources