Transform path with ".." elements to absolute path, in a script? - bash

So when coding a bash/sh script I got a path in a variable with the following content:
$SOMEPATH=/usr/lib64/pkgconfig/../..
How can I convert $SOMEVAR to a full absolute path that converts the ".." folders to this value?:
$SOMEFULLPATH=/usr
Thanks

You could use readlink:
readlink -f "$SOMEPATH"
-f, --canonicalize
canonicalize by following every symlink in every component of
the given name recursively; all but the last component must
exist
-m, --canonicalize-missing
canonicalize by following every symlink in every component of
the given name recursively, without requirements on components
existence

Similar to the answer from #devnull I would recommend readlink, however the -m option does not require any part of the file path to already exist, which is more flexible.
readlink -m $SOMEFULLPATH

Related

Bash/shell/OS interpretation of . and .. — can I define ...?

How do . and .., as paths (vs. ranges, e.g., {1..10}, which I'm not concerned with), really work? I know what they do, and use them all the time, but don't fully grasp how/where they're interpreted. Does the shell handle them? The interpreting process? The OS?
The reason why I'm asking is that I'd like to be able to use ... to refer to ../.., .... to refer to ../../.., etc. (up to some small finite number; I don't need bash to process an arbitrarily large number of dots). I.e., if my current directory is /tmp/let/me/out, and I call cd ..., my resulting current directory should be /tmp/let. I don't particularly care if ... etc. show up in ls -a output like . and .. do, but I would like to be able to call cat /tmp/let/me/out/..../phew.txt to print the contents of /tmp/phew.txt.
Pointers to relevant documentation appreciated as well as direct answers. This kind of syntax question is very hard to Google.
I'm using bash 4.3.42, by the way, with the autocd and globstar shell options.
. and .. are genuine directory names. They are not "sort-cuts", aliases, or anything fake.
They happen to point to the same inode as the other name you use. A file or directory can have several names pointing to the same inode, these are usually known as hard links, to distinguish them from symbolic (or soft) links.
If you are on Linux or OS X you can use stat to look at most of the inode metadata - it is what ls looks at. You will see there is an inode number. If you stat . and stat current-directory-name you will see that number is the same.
The one thing that is not held in the inode is the filename - that is held in the directory.
So . and .. reside in the directory on the file system, they are not a figment of the shell's imagination. So, for example, I can use . and .. quite happily from C.
I doubt you can change them - personally I have never tried and I never will. You would have to change what these filenames linked to by editing the directory. If you managed it you would probably do irreparable damage to your file system.
I write this to clarify what has already been written before.
In many file systems a DIRECTORY is a file; a special type of file that the file system identifies as being distinctly a directly.
A directory file contains a list of names that map to files on the disk
A file, including a directly does not have an intrinsic name associated with it (not true in all file systems). The name of a file exists only in a directory.
The same file can have an entry in multiple directories (hard link). The same file can then have multiple names and multiple paths.
The file system maintains in every directory entries for "." and ".."
In such file systems there are always directory ENTRIES for the NAMES "." and "..". These entries are maintained by the file system.
The name "." links to its own directory.
The name ".." links to the parent directory EXCEPT for the top level directory where it links to itself (. and .. thus link to the same directory file).
So when you use "." and ".." as in /dir1/dir2/../dir3/./dir4/whatever,
"." and ".." are processed in the exact same way as "dir1" and "dir2".
This translation is done by the file system; not the shell.
cd ...
Does not work because there is no entry for "..." (at least not normally).
You can create a directory called "..." if you want.
You can actually achieve something like this, though this is an ugly hack:
You can run a command before every command entered to bash, and after every command. For that you trap the DEBUG pseudo signal and set a command to PROMPT_COMMAND, respectively.
trap 'ln -s ../.. ... &>/dev/null | true' DEBUG
PROMPT_COMMAND='rm ...'
With this, it seems like there's an additional entry in the current directory:
pwd
# /tmp/crazy-stuff
ls -a
# . .. ... foo
ls -a .../tmp/crazy-stuff
# . .. ... foo
Though this only works in the current directory, because the symbolic links is deleted after each command invokation. Thus ls foo/bar/... won't work this way.
Another ugly hack would be to "override" mkdir such that it populates every new directory with these symbolic links.
See also the comments on the second answer here, particularly Eliah's: https://askubuntu.com/questions/327126/what-is-a-dot-only-named-folder
Much in the same way that when you cd into some directory subdir, you're actually following a pointer that points to that directory, .. is a pointer added by the OS that points to the parent directory, and I'd imagine . works the same way.

Delimit the file name while moving to another directory in Shell

Im trying to move multiple files from one directory to another directory.
File name is with sequence and will be varying.
Example:
/global/userhome/usrsats/---------directory which has file names as below:
fl_cl_filename1
fl_cl_filename2
fl_cl_filename3
...
...
Now when moved to another directory, i need to get only the file name and delimit the fl_cl part.
Please help
Assuming you're using bash, I would do this with the remove the matching prefix pattern facility like this (with DEST_DIR set to the destination directory):
cd /global/userhome/usrsats
for f in *; do mv $f ${DEST_DIR}/${f#fl_cl_}; done

What is wrong in the for loop I use to create a file source=dest list?

I try to loop over the file I findin a relative path to build a list of relative path/soure file name=source file name
SHARED_LIB_PACK=""
for LIB in $(find ../level1/leve2/ -name "*.so*")
do
$SHARED_LIB_PACK=$SHARED_LIB_PACK" "$LIB"="${LIB##*/}
done
but as I run it, it complain :
line 6: = ../level1/level2/file.so.1.0=file.so.1.0: No such file or directory
Any help will be welcome
Firstly, variable assignment is done via:
FOO="bar"
and not
$FOO="bar"
The former will not work.
Secondly, your quotes seem to be in strange places:
SHARED_LIB_PACK=$SHARED_LIB_PACK" "$LIB"="${LIB##*/}
should probably be
LIB="${LIB##*/}"
SHARED_LIB_PACK="$SHARED_LIB_PACK $LIB"
or
SHARED_LIB_PACK="$SHARED_LIB_PACK ${LIB##*/}"

How to copy only new files using bash scripting

I have to use bash scripting to copy files from one folder to another. If the destination folder has a file with the same name but older timestamp, it should not copy. Only newer files should be copied. I could have used cp -u, but I was asked not to use it. Essentially I have to use the test command testing for "ot". Please let me know how could this be done. I believe two for loops one to read the files in the source and one for the destination directories can be used and the the time stamp compared. The problem is that both for loops produce the absolute path names along with the file name. So not sure how to compare them
Thanks
You can profit from the parameter substitution:
for file in "$folder1"/* ; do
filename=${file##*/} # Remove everything to the last slash.
Or, you can change the directory:
cd "$folder1"
for file in * ; do
## you have to use full or relative path to $folder2 here

How to match string/dir in a path using bash scripting

I'm trying to create a Makefile that uses information from the path to create a relevant rpm name. Suppose I have two different possible paths:
PATH1 = /usr/local/home/jsmith/code/main
PATH2 = /usr/local/home/jsmith/code/dev/ver2
If "main" is detected in the path, I want to detect and append "main" to the rpm name. If "dev" is detected in the path, I want to detect and append "ver2" to the rpm name.
I'm new to shell scripting and really don't have a good idea on where to start. I could easily do this in something like python, but its for a Makefile so I need to do it in shell.
"main" in the path would be constant, but if "main" doesn't exist, the dev path name would need to be extracted. Here's a few mow examples:
/usr/local/home/jsmith/code/main
/usr/local/home/jsmith/code/dev/ver_usa
/usr/local/home/jsmith/code/dev/ver_mexico
/usr/local/home/jsmith/code/dev/ver3
If "dev" existed, it would be needed to extract "ver_usa", "ver_mexico", "ver3", etc. The dir name needing to be extracted would exactly follow "dev".
something like this, assuming "main" and "ver2" are not constant
some_rpm_name="some rpm"
PATH=/usr/local/home/jsmith/code/main
#PATH2=/usr/local/home/jsmith/code/dev/ver2
s=${PATH##*/}
case "$s" in
*main ) RPM_NAME="${some_rpm_name}_main";;
*ver2) RPM_NAME="${some_rpm_name}_ver2";;
esac
echo $RPM_NAME
Use grep and check for return value?
Also, you can run python in Makefile.
It looks like the last element of the path is always the one you want.
rpmname=$rpmname+${pathname##*/}
or
rpmname=$rpmname+$(basename pathname)

Resources