Unexpected bash autocompletion behavior within a symbolic link loop - bash

I have the following directory structure:
base/
dir/
subdir/
link -> ../dir
Now if I cd to dir/link and type:
cd ../subd[tab]
I get:
cd ../subdir[space]
I would understand if autocomplete fails (because it would canonize the path and look into base/ and not dir/).
I would also understand if it autocompletes to cd ../subdir/ with the ending / (because it would interpret .. as go up one level and search into dir/).
But I do not understand the actual behaviour that is somewhere between the two. Ideally I would like bash to behave like 2. (autocomplete to cd ../subdir/). I am using fedora 14, bash version 4.1.7(1). Any idea how to accomplish this ?

UPDATE: The program with which you can customize auto-completion is called complete.
You can find some good basic examples here: More on Using the Bash Complete Command
Using function and script names as per the above link, here is a script which appends the / to a symbolic link to a directory... It is just a rough sample, but it shows it can be done (I haven't tried it with the cd builtin...
Associate the function _mycomplete_ with executable myfoo
complete -F _mycomplete_ myfoo
The function to go in ~/.bashrc
function _mycomplete_()
{
local cmd="${1##*/}"
local word=${COMP_WORDS[COMP_CWORD]}
local line=${COMP_LINE}
local xpat='!*.foo'
COMPREPLY=($(compgen -f -X "$xpat" -- "${word}"))
if ((${#COMPREPLY[#]}==1)) ;then
[[ -h $COMPREPLY ]] && COMPREPLY="$COMPREPLY/"
fi
}
Original answer:
At the command-line, the main indicator of a auto-expansion to a symbolic link is shown on the last line of the following table, ie. a name expands but without the final /.
on pressing TAB on pressing TAB (again)
what happens? meaning what happens?
=================== ======================= ====================================
Nothing is appended 1=> Multiple sub-dirs exist => A list of possibilities is presented
2=> No sub-directory exists => Nothing is appended (again)
Expands to end in / => A uniquely matching dir => ...as per first column (repeat)
Expands text only => Current name is a link => Expands to end in /
In your example, if you have already primed the command-line to the full name, ie. cd link then the indicator is not obvious. Also you won't know it is a symbolic link via the list of possibilities.
To be able to cd to the link's target, you can use cd -P link, or set -P; cd link

After digging the source code a bit, it looks like this is a bit complicated. The actual problem is a mix between bash allowing symlinks inside the working directory (see pwd -L and pwd -P) and readline not able to determine the type of a match if it is not in a physical directory
In readline/complete.c:1694
s = (nontrivial_match && rl_completion_mark_symlink_dirs == 0)
? LSTAT (filename, &finfo)
: stat (filename, &finfo);
stat() fails since ../ is understood as relative to the physical path and not the logical path. readline fails to determine this is a directory and therefore does not append the final '/'.
A very similar problem is described here
So I guess I can live with the existing behaviour for now...

I was having the exact same problem in Ubuntu. Autocompletion was working like in your example #2, but started working as you describe at some point. I purged and reinstalled the package bash-completion, and now everything seems back to normal. Do not uninstall bash! Only bash-autocompletion.
Edit
look at this:
https://bbs.archlinux.org/viewtopic.php?id=113158

Related

How to remove a file named '.'?

Ok so i did something very stupid (copying a file and renaming it '.') since I thought it would just copy it as .uniprot_sprot.fasta.gz.icloud.
cp /path/.uniprot_sprot.fasta.gz.icloud .
and now I don't know how to remove it from current directory as it would be removing '.' itself.
What can I do?
This doesn't work. It says: No such file or directory
rm .uniprot_sprot.fasta.gz.icloud
On the other hand:
ls -a
gives this:
.
..
uniprot_sprot.fasta.gz.icloud
You have not copied a file and renamed it . (at any rate if you're running a sane *nix). Instead you have copied the file to the current directory with the name of the original file. (If you pass a directory to cp as the destination, files will be placed in that directory. . is the current directory, so this is all that has happened.) If you want to remove it you can just rm uniprot_sprot.fasta.gx.iscloud or explicitly rm ./uniprot_sprot.fasta.gx.iscloud. What you have tried to do is to remove a file whose name starts with ., which is a different thing.
Edit: I was unaware when I wrote this, but this is in fact simply down to . existing as a real, regular hardlink. At syscall level you can create a file whose name contains anything except / and \x00 (yep, including \n), assuming your filesystem allows it. However, the links . and .. are already present and thus unavailable as a file name. #thatotherguy links to the kernel source for the rmdir syscall, showing that in modern Linux at least it is the kernel itself which ultimately prevents you from deleting . and ...
Note that in bash, . at the beginning of a line by itself means source.
See this question on unix.se and its linked dupe for more information on the filename problem.

How to recursively rename all files and folder including specific part of the filename with Windows Bash?

This has to be a duplicate but I have read and tried at least a dozen of Q&As here on SO, and I cannot get any of them working for my case.
Really hope this won't result in downvotes because of it.
So I'm on Windows (10) and have a Bash terminal that I want to use for my task. The MINGW64 one I downloaded when I started working with Git.
I would prefer the solution with this program, but will be perfectly happy with one in Command Prompt Terminal or even PowerShell.
I created a TemplateApp which is in C:\Apps\TemplateApp folder which has multiple folders and subfolders named TemplateApp or TemplateApp.something as well as a lot of files that have TemplateApp as a part of their name.
Could be:
TemplateApp.ext
TemplateApp.something.ext
something.TemplateApp.something.ext
Then I copied the uppermost folder to C:\Apps\TemplateApp - Copy and in turn renamed it to C:\Apps\ProductionApplication.
Now for the love of whomever, I cannot make any of the scripts I found on SO to work for my case, ie. to rename all the above mentioned files and folders by replacing TemplateApp with ProductionApplication.
Here is a bash function I wrote that I think does very much like what you are wanting to do.
function func_CreateSourceAndDestination() {
#
for (( i = 0 ; i < ${#files_syncSource[#]} ; i++ )) ; do
files_syncDestination[${i}]="${files_syncSource[${i}]#${directory_MusicLibraryRoot_source}}"
file_destinationPath="$( dirname -- "${directory_PMPRoot_destination}${files_syncDestination[${i}]}" )"
if [ ! -d "${file_destinationPath}" ] ; then
mkdir -p "${file_destinationPath}"
fi
rsync -rltDvPmz "${files_syncSource[${i}]}" "${directory_PMPRoot_destination}${files_syncDestination[${i}]}"
done
}
In my case I'm feeding into rsync for a source and a destination. I'm pulling all the file paths from an array that has been split into path segments. I have to make certain character substitutions for FAT and NTFS file systems. I do this recursively.
files_syncDestination[${i}]="${files_syncDestination[${i}]//\:/__}"
That's the magic. I load a new array with the character substituted. You could do the same with a loaded variable including your phrases for change.
files_syncDestination[${i}]="${files_syncDestination[${i}]//${targetPhrase}/${subPhrase}}"
After that change in the function, you could use rsync or cp or mv as you prefer to go from your source array to your destination array.
(The double-slash in the substitution makes the substitution global.)

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.

Is there option in cp command to reflect source path as destination path?

If source and destination path are being same, how to skip re-typing it?
For example, I need to take a back-up of a file or rename it:
# taking back-up
$ cp ~/project/uboot/u-boot.img ~/project/uboot/ver1-u-boot.img
# renaming it
$ mv ~/project/uboot/u-boot.img ~/project/uboot/ver1-u-boot.img
It's all about the curly braces!
$ cp ~/project/uboot/{,ver1-}u-boot.img
or to be more verbose
$ mv ~/project/uboot/{u-boot.img,ver1-u-boot.img}
The shell will reproduce what you have explicitly written in your question, which means you can write out the full path once. Here's a good link for further reading.
How about
( cd ~/project/u-name-it; cp_or_mv file ver1-file )
or
base=base-path cp_or_mv $base/file $base/ver1-file
However this very much looks like a use case for a script or function to me.
As these can become quite sophisticated of course (e.g. increasing the version would be nice) I prefer to give a simple example more as an incentive ;)
# Untested, test well before using; should work in zsh & bash IFIAC
# usage:
# bup directory [files...]
function bup
{
local dir=$1
shift
for file; do
cp $dir/$file $dir/ver1-$file
done
}
HTH
Robert

Variable name appears in prompt

I'm trying to automate my daily task of opening my editor and "cd-ing" into a project folder at the same time by typing 'project something'. So far so good. The code is working. But I got some unexpected behavior. The current directory label is showing Lukas-mbp:~sub_directory(2603m|master) $. What is this sub_directory doing there. It is the variable name I'm using, as the code below. But can anyone tell why my terminal is showing that instead of the actual directory?
function project() {
for directory in ~/projects/*
do
for sub_directory in $directory/*
do
if [[ "$sub_directory" =~ $1 ]]; then
cd "$sub_directory"
sublime $sub_directory
return
fi
done
done
}
Use a simple wildcard instead of doing all that unecessary looping! I've not, however, completely understood your subdirectories tree, so I've came with two alternatives, use whichever is appropriated. Add the function to ~/.bashrc.
1 - If your subdirectories tree is like this (works exactly as in your example*):
~/projects/subdir1/target1/
~/projects/subdir1/target2/
~/projects/subdir2/target3/
~/projects/subdir2/target4/
The function will be like this:
function project {
cd ~/projects/*/"$1"
sublime .
# or sublime ./*
}
* Keep in mind that if you have two or more target with the same name, but on different subdir, it will only match the first one, as will your example.
2 - If your subdirectories tree is like this (which seems more common to me):
~/projects/target1/
~/projects/target2/
~/projects/target3/
~/projects/target4/
The function will be like this:
function project {
cd ~/projects/"$1"
sublime .
# or sublime ./*
}

Resources