One-line copy command when source and dest path are the same - linux

I want to backup a file in some-other sub-directory different from my current directory like this:
cp /aaa/bbb/ccc/ddd/eeee/file.sh /aaa/bbb/ccc/ddd/eeee/file.sh.old
As you see both source and dest dir are the same, so common convention would be to change to the common directory, perform the copy im ./, then change back to the original directory.
Is there a single-line command to accomplish the copy in this situation?

Yes. Use this:
cp /aaa/bbb/ccc/ddd/eeee/{file.sh,file.sh.old}
The curly braces will cause the first part of the string to be reused for each of the items separated by commas. Bash is what expands the above into two separate paths and then passes it to cp. To see what Bash would be passing to cp, simply add an echo to the beginning:
echo cp /aaa/bbb/ccc/ddd/eeee/{file.sh,file.sh.old}
You will see that produces your original statement:
cp /aaa/bbb/ccc/ddd/eeee/file.sh /aaa/bbb/ccc/ddd/eeee/file.sh.old
You're just using a Bash trick to save on typing.

Related

Is mv * a destructive command on a directory with 2 or more files? What other linux commands have similar behavior?

When I run mv * with no destination directory on a directory with say 10 files, I get an error as follows
root#tryit-apparent:~/test2# ls
file1.txt file10.txt file2.txt file3.txt file4.txt file5.txt file6.txt file7.txt file8.txt file9.txt
root#tryit-apparent:~/test2# mv *
mv: target 'file9.txt' is not a directory
When I run it on a directory with two files it overwrites the file with one just file.
root#tryit-apparent:~/test# ls
tempfile tempfile2
root#tryit-apparent:~/test# mv *
root#tryit-apparent:~/test# ls
tempfile2
I read the man pages but couldn't understand this behaviour. Would like to know what's causing this behavior and what's going on under the hood?
What other linux commands have such pitfalls and have destructive actions that are executed silently if the user is not aware of such behavior?
In Unix, unlike some other OSes, wildcards like * are expanded by the shell, before being passed to the command being run. So when you run mv * with tempfile and tempfile2 as the only files in the current directory, what the shell actually executes is mv tempfile tempfile2, which as normal will rename the first file over the second one, erasing the previous contents of tempfile2. The shell doesn't know or care that this command treats its last argument specially, and mv has no way of knowing that its two arguments came from a wildcard expansion. Hence the behavior you're seeing.
You can have similar issues even with more than two files. For instance, if you have files named tempfile1 through tempfile9 and a subdirectory named zyzzx, then mv * will move all your temp files into the zyzzx subdirectory.
Mostly, you just have to be aware that this is how wildcards work, and use caution with commands that treat one of their arguments specially (e.g. as a destination). cp is another one to watch out for, for the same reason. For interactive usage, you may want to get used to using the -i option to mv and cp, which asks for confirmation before overwriting files; or use an alias to make this the default.
Move is intented to move or rename a file or a directory, so you need a source and a destination.
If the path of the file is unchange then it becomes a rename operation.
If the path changes and the name remains the same it's a move.
You can do both by chaning the path and the name.
Man pages can be challenging to wrap your head around.
Googling can help: https://www.howtoforge.com/linux-mv-command/
Off the top of my head, you could do a cp operation followed by a rm to achieve similar results, but that's two steps, rather than one.

Copy or move with combining source and destination path to avoid long path repeated

I need to edit many python files. When I about to start editing a file, I just create a copy of the file and will compare to this copy after I am finished editing and save changes to original file.
So I tend to work from one fixed location/path and edit/copy files in different paths by using their absolute path. I end up providing complete path for source file and complete path for destination file.
How can I use unix cp command which avoids mentioning path twice when both files are to be in same directory/path.
I have tried the traditional copy command: cp source-file target-file. But I had to repeat the path twice. For example:
cp /main/dept_1/class_2/get_list.py /main/dept_1/class_2/copy_get_list.py
There is a different way to try this but I forgot exact syntax, but it goes this way:
cp /main/dept_1/class_2/get_list.py[copy_get_list.py]
I expect to mention the path only once and be able to provide source and destination file names in copy (cp) command.
$home:ls /main/dept_1/class_2/
get_list.py
$home:cp /main/dept_1/class_2/get_list.py[copy_get_list.py]
I get error: "cp: missing destination file operand after"
Use curly braces:
cp /main/dept_1/class_2/{,copy_}get_list.py
When you have a brace list in a word, the word is repeated with each list element replacing the brace list.

rsync - copy files with same name

I have some different files with the same name and I want to copy all of them to the destination which has a flat structure (no directories, just files), is there any way to append some text onto one of the file names so that both can be copied.
Need to use rsync because there are some files that I need to exclude from the copy.
For example:
dir1/file1.txt
dir1/dir2/file1.txt
both get copied, and in the destination there is:
file1.txt
file1.txt.txt
typically, when I want to do some complex name-mungling, I just write the list of files (with find dir1 >listfiles) and fix it with a text editor.
for example, s/^.*\/([^\/]+)$/cp \0 destination/\1/ converts a file like
dir1/file1.txt
dir1/dir2/file1.txt
to a script like:
cp dir1/file1.txt destination/file1.txt
cp dir1/dir2/file1.txt destination/file1.txt
then you could do something like cut -f 3 <listfiles | sort | uniq -d to find those with the same destination filename. then go back to the editor and fix those lines.
After a few minutes you get a full script for exactly the copy you want, without surprises because you can see each command and apply the best fix for each case.
As far as i know there is no default option in rsync to do that. But i guess that since you are copying files with the same name but from different directories, you are using
multiple rsync commands.
So, this gives you two options:
Create folders..
rsync -av /home/user1/file1 /media/foo/user1/file1
rsync -av /home/user2/file1 /media/foo/user2/file1
etc..
or rename the files with an id
rsync -av /home/user1/file1 /media/foo/parent_dir-file1
rsync -av /home/user2file1 /media/foo/parent_dir-file1
etc..
If you want to use the second solution you can build a simple script. As you are using rsync i suppose that you know the basics on GNU-Linux, so a simple bash script would be enough!
A basic ID is to get the parent folder name and add it as variable to the path of the rsync command. ( it won't always work )
IF you want to be sure of a good id you can for example set a counter and increment like
file1-1
file1-2
file1-3
But you will loose the track of its absolute path.
All the solutions can work, its up to you to choice the one that feed your needs!

shell script Move and Copy

I have a folder named rules in
/srv/www/htdocs/downloads
and I have another folder with the same name rules in here:
/srv/www/htdocs/didebansnort/core/snort/
I want to copy the folder rules in upper one to the second path
Also I want to know what is the syntax for moving?
the same that you use when you copy/move things in shell. That's why they called shell scripts:)
Copying (from -> to)
cp -r /srv/www/htdocs/downloads/rules /srv/www/htdocs/didebansnort/core/snort/rules
we use cp -r to copy directory (r stands for recursive)
and simple cp for copying of a single file.
Moving (from -> to)
mv /srv/www/htdocs/downloads/rules /srv/www/htdocs/didebansnort/core/snort/rules
This is for nix/mac/cygwin
P.S.
While in the shell you can simply type in this to get a complete manual:
man command_name
If the directory /srv/www/htdocs/didebansnort/core/snort/rules is not empty you cannot just move /srv/www/htdocs/downloads/rules to /srv/www/htdocs/didebansnort/core/snort. You'll either need to delete the existing directory or devise some merging strategy. Fore example, copying over cp -r /srv/www/htdocs/downloads/rules /srv/www/htdocs/didebansnort/core/snort/rules (as nix showed) will result in overwriting all duplicate files.

How to directly overwrite with 'unexpand' (spaces-to-tabs conversion)?

I'm trying to use something along the lines of
unexpand -t 4 *.php
but am unsure how to write this command to do what I want.
Weirdly,
unexpand -t 4 file.php > file.php
gives me an empty file. (i.e. overwriting file.php with nothing)
I can specify multiple files okay, but don't know how to then overwrite each file.
I could use my IDE, but there are ~67000 instances of to be replaced over 200 files, and this will take a while.
I expect that the answers to my question(s) will be standard unix fare, but I'm still learning...
You can very seldom use output redirection to replace the input. Replacing works with commands that support it internally (since they then do the basic steps themselves). From the shell level, it's far better to work in two steps, like so:
Do the operation on foo, creating foo.tmp
Move (rename) foo.tmp to foo, overwriting the original
This will be fast. It will require a bit more disk space, but if you do both steps before continuing to the next file, you will only need as much extra space as the largest single file, this should not be a problem.
Sketch script:
for a in *.php
do
unexpand -t 4 $a >$a-notab
mv $a-notab $a
done
You could do better (error-checking, and so on), but that is the basic outline.
Here's the command I used:
for p in $(find . -iname "*.js")
do
unexpand -t 4 $(dirname $p)/"$(basename $p)" > $(dirname $p)/"$(basename $p)-tab"
mv $(dirname $p)/"$(basename $p)-tab" $(dirname $p)/"$(basename $p)"
done
This version changes all files within the directory hierarchy rooted at the current working directory.
In my case, I only wanted to make this change to .js files; you can omit the iname clause from find if you wish, or use different args to cast your net differently.
My version wraps filenames in quotes, but it doesn't use quotes around 'interesting' directory names that appear in the paths of matching files.
To get it all on one line, add a semi after lines 1, 3, & 4.
This is potentially dangerous, so make a backup or use git before running the command. If you're using git, you can verify that only whitespace was changed with git diff -w.

Resources