A short script to incorporate filesystem changes for people who dual-boot

:warning: Despite my best efforts, people will misuse this script. Mainly because they probably won’t slow down to read everything in this post. I’ve had done everything I can for people to second-guess, double-check and confirm actions before committing, but this isn’t for complete newbies.

Please read before you inadvertently destroy your filesystem.
I’m not being dramatic, I’m serious.

I’ve written countless times about how to symbolically link or configure XDG directories for creating filesystems which utilize the existing directories generated by Microsoft Windows or at another persistent, at-boot mountpoint defined in /etc/fstab for creating a dual-boot environment which doesn’t bog down the user with redundancy and duplicate directories / files, but I understand how difficult it may be for some who aren’t familiar with the concept to perform such actions.

With this understanding, I’ve developed a basic script with a menu. Because I wish not to cause any concern for malicius software, I am going to initially share this script here for @moderators to observe, check and vallidate it is clean code and good for the community before I create a file in my GitHub for other people to see, and also to accept input by people who know scripting better than I do. A lot of this came from examples and discussions online, which was effectively my crash course for Bash.

This is also the first time I made a good choice menu in Bash. Once I had a basic understanding and found enough generated examples I was able to create this script which has very few provisions to prevent users from screwing up. So here’s the script…

# Migration script for transferring contents from an existing Linux home directory to mounted shared partition
#
# Intended for people who dual-boot, the partition where personal effects will be shared between Linux and Windows
# should be configured in 'gnome-disks' or manually by editing 'admin:///etc/fstab' before using this script.

read_me='You should not be using this yet.'
no_really='Pick \e[1moption 3\e[0m and modify me.'
# Once finished, you can disable deug output by commenting line 126, like this.
# Don't forget to read notes before proceeding to edit this file!

#=== VARIABLES ===#
# Adjust these as necessary. DO NOT execute this script without adjusting them.
nixPart=$read_me
newPart=$no_really
# For these variables, see note 3.
oldUser=$USER
oldPath=$nixPart/$oldUser
newUser=$USER
newPath=$newPart/$newUser

# Copy behaviour, see note 4.
# copyAction='cp -iR'

#=== NOTES ===#
# 1: This is the directory path to your personal effects in Linux, sans user. Typically this is
###| /home but it should be changed per your system.
#¯¯
# 2: This is the directory path to your personal effects in WIndows, sans user. Typically this is
###| [Windows OS filesystem mountpoint]/Users but it should be changed per your system.
#¯¯
# 3: $USER assumes both mount points will lead to the same directory if using the same output as
###| 'echo $USER' for each. If not, change the user directories for Windows and Linux.
###|
###| due to Microsoft's internet synchronization features your account name may be using your
###| Microsoft account username, truncated to the fifth letter so it will most certainly be
###| different if you used Microsoft credentials and have your Linux username be as you desire.
###|
###| If you use a shared partition instead, BLANK $newUser and $newPart ('') to define $newPath
###| by hand so it refers to the mountpoint where all of your personal effects will be.
#¯¯
# 4: Change the command above to the following if no interaction is desired. (Incl. recursive cp.)
###| 'cp -R': Overwrite if duplicate exists
###| 'cp -nR': Do nothing if duplicate exists
#¯¯
# 5: All mountpoints MUST be configured before using this script. As to be expected since you
###| have to define these paths in the variables above anyway.
#¯¯

### END USER CONFIGURATION SECTION ###

#=== DEBUG ===#
shDbg() {
echo -e DEBUG: Variable output shown
echo -e User 1.... $oldUser
echo -e User 2.... $newUser
echo -e Linux..... $nixPart
echo -e Windows... $newPart
echo -e Old dir... $oldPath
echo -e New dir... $newPath
echo ""
}

#=== PREPARATION ===#
doXfer() {
	# Prompt menu for action
	echo -e
	echo -e "Before continuing, there are multiple ways to handle duplicates."
	echo -e "If such exist in $oldPath exist during transfer,"
	echo ""
	echo -e "what should this script do?"
	echo -e "---------------------------"
	PS3="Action to take: "
	select cpAction in "Ask for each instance" "Overwrite duplicates" "Ignore duplicates"
	do
		case $cpAction in
			"Ask for each instance")
				copyAction='cp -iR'
				echo -e "You will be asked for each file."
				read -p "$pak $nextTask"
				break
			;;
			"Overwrite duplicates")
				copyAction='cp -R'
				echo -e "All duplicates in $newPath will be destoryed."
				read -p "$pak $nextTask"
				break
			;;
			"Ignore duplicates")
				copyAction='cp -nR'
				echo -e "All duplicates in $newPath will be preserved."
				read -p "$pak $nextTask"
				break
		esac
	done
	# Copy from Linux directories existing files
	$copyAction $oldPath/Documents/* $newPath/Documents
	$copyAction $oldPath/Downloads/* $newPath/Downloads
	$copyAction $oldPath/Music/* $newPath/Music
	$copyAction $oldPath/Pictures/* $newPath/Pictures
	$copyAction $oldPath/Videos/* $newPath/Videos
}

#=== SYMBOLIC LINKS ===#
doLinks() {
	### This process will replace original directories with linkx, but without losing any files there might have been made.
	# Define symbolic links
	ln -s $newPath/Documents $oldPath/Documents
	ln -s $newPath/Downloads $oldPath/Downloads
	ln -s $newPath/Music $oldPath/Music
	ln -s $newPath/Pictures $oldPath/Pictures
	ln -s $newPath/Videos $oldPath/Videos
}

#=== XDG UPDATE ===#
doXDG() {
	### This process will delete original directories, but without losing any files there might have been made.
	# xdg1: Define XDG paths
	xdg-user-dirs-update --set DOCUMENTS $newPath/Documents
	xdg-user-dirs-update --set DOWNLOAD $newPath/Downloads
	xdg-user-dirs-update --set MUSIC $newPath/Music
	xdg-user-dirs-update --set PICTURES $newPath/Pictures
	xdg-user-dirs-update --set VIDEOS $newPath/Videos
}

#=== CLEANUP ===#
doRm() {
	rm -rf $oldPath/Documents $oldPath/Downloads $oldPath/Music $oldPath/Pictures $oldPath/Videos
}

#=== MESSAGE TEXT ===#
intro() {
	echo -e "Greetings $USER on machine $HOSTNAME. This script will — in one of"
	echo -e "two ways — transfer contents from your current system instance to"
	echo -e "a location defined by you."
	echo ""
	echo -e "Prior to making a selection, you should already have this script"
	echo -e "configured to use paths you had specified. If you had not opened"
	echo -e "this document to modify it yet, pick \e[1moption 3\e[0m and use"
	echo -e "your text editor to make necessary changes."
	echo ""
	echo -e "Select an action below:"
	echo -e "-----------------------"
}
msg1() {
	echo ""
	echo -e "The next series of actions will perform as follows"
	echo -e "\e[3mon your behalf\e[0m:"
	echo ""
	echo -e "- Copy files from the \"big five\" directories in"
	echo -e "$oldPath to $newPath"
}
msg2() {
	echo ""
	echo -e "If you accept these actions, press any key,"
	read -p "If you deny these actions, perform ^C now."
}
# Canned lines
pak="Press any key to"
nextTask="continue or ^C to cancel."


#=== MENU ===#
shDbg
intro
PS3="Selection: "
select action in "Make links to new location" "Use XDG for new location" "Do nothing"
do
	case $action in
		"Make links to new location")
			msg1
			echo -e "- Delete redundant directories from $oldPath"
			echo -e "- Create symbolic links from $oldPath to $newPath"
			msg2
			doXfer
			doRm
			doLinks
			echo ""
			read -p "Finished! $pak exit."
			exit
		;;
		"Use XDG for new location")
			msg1
			echo -e "- Redefine XDG paths so they go to $newPath"
			echo -e "- Delete redundant directories from $oldPath"
			msg2
			doXfer
			doXDG
			doRm
			echo ""
			read -p "Finsihed! $pak exit."
			exit
		;;
		"Do nothing")
			read -p "Nothing has been done. $pak exit."
			exit
	esac
done

…And an explanation of specific script features.

Functions

doXfer

Copies from $oldPath (in Linux filesystem) to $newPath (Windows filesystem / shared filesystem). This is before directories in $oldPath are destroyed, so this should generally go quickly for people who are just starting to dual-boot between WIndows and Manjaro.

The new version of this script wiill also prompt for action and define what become $copyAction.

doRm

Deletes directories in $oldPath. Executes after doXfer.

doLinks

Makes symbolic links from $newPath to $oldPath.

doXDG

Reconfigures XDG using xdg-user-dirs-update. Through that, it changes what is defined in $HOME/.config/xdg-user-dirs.dirs so if you’d like, back that up before changes are made.

shDbg

Commented in line 124. Remove hashtag to enable a “Debug” feature which lists paths prior to the introduction.

intro, msg1, msg2

Flavour text. Reduces redundancy by recycling echoes.

Variables

$oldPart

User-defined location for path to Linux system. Typically this is $HOME.

$newPart

Path to Microsoft Windows. Usually at NTFS / exFAT mountpoint for Widows in Users/.

Can also be path to other partition used to save personal effects, but ti assumes the paths already exist.

$oldUser

Name of user in Linux. Usually this would be $USER.

$newUser

Name of user in Windows. Usually this doesn’t match, so it must be defined by hand. If it does however (or if you used the same name for $USER as shown when invoking explorer with %userprofile% / as seen in Users/) then logically this wold be $USER also.

$oldPath

$oldPart/$oldUser

$oldPath

$newPart/$newUser

$copyAction

Defined in doXfer. Will either do interactive, clobber or no-clobber.

Script use

Change variables to represent paths in Linux and Wndows filesystems / shared mountpoint and execute. Script will not function unless $oldUser and $newUser is defined.

In short this script will create links or redefine XDG paths for Documents, Downloads, Music, Pictures and Videos so that copies in a new location are made accessible in the Linux filesystem. Make sure you edit /etc/fstab by hand or through gnome-disks so that whatever $newPath is will be mounted at startup prior to execution so issues with non-existant directories are avoided.

Debug mode is automatically enabled for this revision.

I had tested this script in an isolated directory environment multiple times to make sure the script performs as intended. Please take considerable time making sure everything is correct.

For this revision I also made it generic, so there is no reference to Microsoft Windows beyond what might be in notes.

1 Like

Looks neat as a start. Instead of hard coding the variables, you could write an input dialog for them or parse the command line arguments with getopts.

Also some sanity checks and error handling might be in order if you want to make it robust.

I’ve done this type of symlink setup a few times by hand. It’s quite useful with dualbooting.

I haven’t looked at the script thoroughly. Maybe I’ll a take a look at it tomorrow.

However, for those who dual boot, why not have a shared NTFS partition? Create symlinks of each folder in /home/<user-name>/ (for example Documents, Pictures, etc.) to the shared partition. And on Windows set the location of these directories on the new partition as well (you can do this since Windows 7 - you literally tell the system where you want these user directories - on Windows XP you had to use symlinks to achieve this).

It’s much simpler, no copying. You just need to remember to never hibernate one OS and then boot the other, because the booted system may not see fs changes made by hibernated one and vice-versa. On Windows you also need to disable fast-boot.

I used this scheme for many years without problems. In fact, I’m so used to it that even today I use a data partition (a linux one) with those directories symlinked to my home directory. I then share that partition with my Windows VMs (though I don’t change the default user directories anymore.

A small issue I take with that is even as a user of open-source systems and tools which does this very thing you speak of, I personally feel it would be more intimidating to an end-user to punch in a path in-situ because that can lead to even more screwing up.

At least, as it is right now the few sanity check is that the script breaks on execution as it is published right now and the user is reminded to double-check as they use it. This would then force a user to open it, read the notes and use their file manager to grab a few bits from the address bar so they can hard-code the variables for their system.

Generally, this is a one-and-done script. Once it’s executed it would probably become binned or ignored. So as indispensable as this is for end-users, I also consider it disposable, like a cheap bottle of water.

Already there. And this script can be used for that purpose. Didn’t see this part of the message until later but figured I would chime in saying that’s exactly my setup right now. In Microsoft Windows, something similar to XDG directories can be done in Explorer by right-clicking on library directories and changing their targets. Those update registry keys for local user which define these directories for library locations similar to how xdg-user-dirs-config does for xdg-user-dirs.dirs.

I might update this script later to be less specific about Microsoft Windows, might give that a think.

I prefer the path completion of the shell to manually checking the paths in a file. You could also do an interactive path selection with fzf, dialog, ranger or zenity.

I have no idea about any of that. What would be the easiest way to integrate an interactive shell prompt in the script?

Also, modified the script to ask about what $copyAction does in function doXfer.