Apa tujuan menggunakan shift dalam skrip shell?

118

Saya telah menemukan skrip ini:

#! /bin/bash                                                                                                                                                                                           

if (( $# < 3 )); then
  echo "$0 old_string new_string file [file...]"
  exit 0
else
  ostr="$1"; shift
  nstr="$1"; shift  
fi

echo "Replacing \"$ostr\" with \"$nstr\""
for file in $@; do
  if [ -f $file ]; then
    echo "Working with: $file"
    eval "sed 's/"$ostr"/"$nstr"/g' $file" > $file.tmp 
    mv $file.tmp $file
  fi  
done

Apa arti garis yang digunakan shift? Saya kira skrip harus digunakan dengan setidaknya argumen jadi ...?

Patryk
sumber

Jawaban:

141

shiftadalah bashbuilt-in yang menghapus argumen dari awal daftar argumen. Mengingat bahwa 3 argumen yang diberikan untuk script yang tersedia di $1, $2, $3, maka panggilan untuk shift akan membuat $2baru $1. Sebuah shift 2akan bergeser oleh dua membuat baru $1yang lama $3. Untuk informasi lebih lanjut, lihat di sini:

humanityANDpeace
sumber
25
+1 Perhatikan itu tidak memutar mereka, itu menggeser mereka dari array (argumen). Shift / unshift dan pop / push adalah nama umum untuk jenis manipulasi umum ini (lihat, misalnya, bash's pushddan popd).
goldilocks
1
Referensi definitif: gnu.org/software/bash/manual/bashref.html#index-shift
glenn jackman
@goldilocks sangat benar. Alih-alih "memutar" saya seharusnya menemukan cara untuk menggunakan kata lain, lebih baik seperti "memajukan argumen dalam variabel argumen". Pokoknya saya berharap contoh dalam teks dan tautan ke referensi akan membuatnya tetap jelas.
humanityANDpeace
Meskipun naskah menyebutkan bash, pertanyaannya adalah umum untuk semua shell, oleh karena itu standar Posix menang: pubs.opengroup.org/onlinepubs/9699919799/utilities/…
calandoa
32

Seperti yang dijelaskan oleh komentar goldilocks dan referensi kemanusiaan, shifttetapkan kembali parameter posisi ( $1,, $2dll.) Sehingga $1mengambil nilai lama $2, $2mengambil nilai $3, dll. *   Nilai lama $1dibuang. ( $0tidak diubah.) Beberapa alasan untuk melakukan ini termasuk:

  • Ini memungkinkan Anda mengakses argumen kesepuluh (jika ada) dengan lebih mudah.  $10tidak berfungsi - ini diartikan sebagai $1digabungkan dengan 0 (dan mungkin menghasilkan sesuatu seperti Hello0). Setelah a shift, argumen kesepuluh menjadi $9. (Namun, di sebagian besar shell modern, Anda dapat menggunakan ${10}.)
  • Seperti yang diperlihatkan oleh Bash Guide for Beginners , Bash Guide dapat digunakan untuk mengulang argumen. IMNSHO, ini canggung; forjauh lebih baik untuk itu.
  • Seperti pada skrip contoh Anda, memudahkan untuk memproses semua argumen dengan cara yang sama kecuali beberapa. Misalnya, dalam skrip Anda, $1dan $2merupakan string teks, sementara $3dan semua parameter lainnya adalah nama file.

Jadi begini caranya. Misalkan skrip Anda dipanggil Patryk_scriptdan disebut sebagai

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3

Script melihat

$1 = USSR
$2 = Russia
$3 = Treaty1
$4 = Atlas2
$5 = Pravda3

Pernyataan ostr="$1"mengatur variabel ostrmenjadi USSR. shiftPernyataan pertama mengubah parameter posisi sebagai berikut:

$1 = Russia
$2 = Treaty1
$3 = Atlas2
$4 = Pravda3

Pernyataan nstr="$1"mengatur variabel nstrmenjadi Russia. shiftPernyataan kedua mengubah parameter posisi sebagai berikut:

$1 = Treaty1
$2 = Atlas2
$3 = Pravda3

Dan kemudian forperubahan lingkaran USSR( $ostr) ke Russia( $nstr) dalam file Treaty1, Atlas2dan Pravda3.



Ada beberapa masalah dengan skrip.

  1. untuk file dalam $ @; melakukan

    Jika skrip dipanggil sebagai

    Patryk_script USSR Russia Treaty1 "World Atlas2" Pravda3

    ia melihat

    $ 1 = USSR
    $ 2 = Rusia
    $ 3 = Perjanjian1
    $ 4 = World Atlas2
    $ 5 = Pravda3

    namun, karena $@tidak dikutip, ruang di World Atlas2tidak dikutip, dan forlingkaran berpikir itu memiliki empat file: Treaty1, World, Atlas2, dan Pravda3. Ini juga harus

    untuk file dalam "$ @"; melakukan

    (Mengutip karakter khusus dalam argumen) atau secara sederhana

    untuk file lakukan

    (yang setara dengan versi yang lebih panjang).

  2. eval "sed / s /" $ ostr "/" $ nstr "/ g '$ file"

    Ini tidak perlu menjadi eval, dan meneruskan input pengguna yang tidak dicentang ke evalbisa berbahaya. Misalnya, jika skrip dipanggil sebagai

    Patryk_script "'; rm *;'" Russia Treaty1 Atlas2 Pravda3

    itu akan mengeksekusi rm *! Ini adalah masalah besar jika skrip dapat dijalankan dengan hak istimewa lebih tinggi dari pada pengguna yang memintanya; misalnya, jika dapat dijalankan melalui sudoatau dipanggil dari antarmuka web. Ini mungkin tidak begitu penting jika Anda hanya menggunakannya sebagai diri sendiri, di direktori Anda. Tapi itu bisa diubah menjadi

    sed "s / $ ostr / $ nstr / g" "$ file"

    Ini masih memiliki beberapa risiko, tetapi mereka jauh lebih ringan.

  3. if [ -f $file ], > $file.tmpdan mv $file.tmp $file harus if [ -f "$file" ], > "$file.tmp"dan mv "$file.tmp" "$file", masing-masing, untuk menangani nama file yang mungkin memiliki spasi (atau karakter lucu lainnya) di dalamnya. ( eval "sed …Perintah juga mangles nama file yang memiliki spasi di dalamnya.)


* shift mengambil argumen opsional: bilangan bulat positif yang menentukan berapa banyak parameter yang bergeser. Standarnya adalah satu ( 1). Misalnya, shift 4menyebabkan $5 menjadi $1, $6menjadi $2, dan seterusnya. (Perhatikan bahwa contoh dalam Bash Guide for Beginners salah.) Dan skrip Anda dapat dimodifikasi untuk mengatakan

ostr="$1"
nstr="$2"
shift 2

yang mungkin dianggap lebih jelas.



Catatan Akhir / Peringatan:

Bahasa Windows Command Prompt (file batch) juga mendukung SHIFTperintah, yang pada dasarnya melakukan hal yang sama dengan shiftperintah di shell Unix, dengan satu perbedaan mencolok, yang akan saya sembunyikan untuk mencegah orang-orang menjadi bingung olehnya:

  • Perintah like SHIFT 4adalah kesalahan, menghasilkan pesan kesalahan “Parameter tidak valid ke perintah SHIFT”.
  • SHIFT /n, di mana nbilangan bulat antara 0 dan 8, valid - tetapi tidak bergeser kali . Ini menggeser sekali, dimulai dengan yang n  th argumen. Jadi menyebabkan (argumen kelima) menjadi , dan seterusnya, meninggalkan argumen 0 hingga 3 saja.n SHIFT /4%5%4,  %6%5

Scott
sumber
2
shiftdapat diterapkan ke while,, untildan bahkan forloop untuk menangani array dengan cara yang jauh lebih halus daripada forloop sederhana . Saya sering menemukan itu berguna dalam forloop seperti ... set -f -- $args; IFS=$split; for arg do set -- $arg; shift "${number_of_fields_to_discard}" && fn_that_wants_split_arg "$arg"; done
mikeserv
3

Penjelasan paling sederhana adalah ini. Pertimbangkan perintahnya:

/bin/command.sh SET location Cebu
/bin/command.sh SET location Cebu, Philippines 6014 

Tanpa bantuan shiftAnda, Anda tidak dapat mengekstraksi nilai lengkap lokasi karena nilai ini bisa panjang secara sewenang-wenang. Ketika Anda menggeser dua kali, argumen SETdan locationdihapus sedemikian rupa sehingga:

x="$@"
echo "location = $x"

Menatap hal itu sangat lama $@. Itu berarti, itu dapat menyimpan lokasi lengkap ke dalam variabel xterlepas dari spasi dan koma yang dimilikinya. Jadi dalam ringkasan, kita panggil shiftdan kemudian kita mengambil nilai dari apa yang tersisa dari variabel $@.

PEMBARUAN Saya menambahkan di bawah ini cuplikan yang sangat singkat yang menunjukkan konsep dan kegunaan shifttanpanya, akan sangat, sangat sulit untuk mengekstrak bidang dengan benar.

#!/bin/sh
#

[ $# -eq 0 ] && return 0

a=$1
shift
b=$@
echo $a
[ -n "$b" ] && echo $b

Penjelasan : Setelah itu shift, variabel b harus berisi sisa barang-barang yang diteruskan, tidak peduli ruang atau lainnya. Ini [ -n "$b" ] && echo $badalah perlindungan sehingga kami hanya mencetak b jika memiliki konten.

typelogic
sumber
(1) Ini bukan penjelasan yang paling sederhana. Ini sebuah sudut yang menarik. (2) Tetapi selama Anda ingin menggabungkan banyak argumen (atau semuanya), Anda mungkin ingin membiasakan diri menggunakan "$*"alih-alih "$@". (3) Saya akan mengubah jawaban Anda, tetapi saya tidak melakukannya, karena Anda mengatakan "bergeser dua kali" tetapi Anda tidak benar-benar menunjukkannya dalam kode. (4) Jika Anda ingin skrip dapat mengambil nilai multi-kata dari baris perintah, mungkin lebih baik meminta pengguna untuk memasukkan seluruh nilai ke dalam tanda kutip. … (Lanjutan)
Scott
(Lanjutkan) ... Anda mungkin tidak peduli hari ini, tetapi pendekatan Anda tidak memungkinkan Anda memiliki banyak ruang ( Philippines   6014), ruang utama, spasi tambahan, atau tab. (5) BTW, Anda mungkin juga dapat melakukan apa yang Anda lakukan di sini dengan bash x="${*:3}".
Scott
Saya mencoba menguraikan dari mana '*** Anda tidak mengizinkan banyak spasi ***' berasal dari karena ini tidak terkait dengan shiftperintah. Anda mungkin merujuk ke perbedaan halus $ @ versus $ *. Tetapi kenyataannya masih tetap, bahwa cuplikan saya di atas dapat secara akurat mengekstraksi lokasi tidak peduli berapa banyak ruang yang telah diikutinya atau di depannya tidak masalah. Setelah shift, lokasi akan berisi lokasi yang benar.
typelogic
2

shift memperlakukan argumen baris perintah sebagai antrian FIFO, itu elemen popleft setiap kali itu dipanggil.

array = [a, b, c]
shift equivalent to
array.popleft
[b, c]
$1, $2,$3 can be interpreted as index of the array.
$# is the length of array
Aljabar
sumber